Bulkification & Governor Limits Mastery

0/9 in this phase0/41 across the roadmap

๐Ÿ“– Concept

Bulkification is the most important concept in Salesforce development. It means writing code that efficiently handles 1 record or 200 records (or 10,000 in batch) with the same resource consumption pattern.

Why 200? When records are saved via Data Loader, API, or trigger re-firing, up to 200 records are processed in a single transaction (the chunk size). Your trigger fires ONCE for all 200 records, not 200 times.

The Bulkification Pattern:

1. Collect โ€” Gather all needed data (IDs, field values) from Trigger.new
2. Query โ€” Execute SOQL ONCE with collected data (WHERE IN :ids)
3. Map โ€” Build Map<Id, SObject> for O(1) lookups
4. Process โ€” Iterate and apply logic using the Map
5. DML โ€” Execute DML ONCE with all modified records

Governor Limits โ€” Complete Reference:

                              Synchronous    Asynchronous
SOQL Queries                      100            200
SOQL Rows Retrieved            50,000         50,000
DML Statements                   150            150
DML Rows                       10,000         10,000
CPU Time                     10,000ms       60,000ms
Heap Size                        6 MB          12 MB
Callouts                        100            100
Future Methods                   50             50 (from batch)
Queueable Jobs                   50             1 (from another queueable)
Email Invocations                10             10
SOSL Searches                    20             20

Why limits are per-transaction, not per-trigger: If a trigger on Account causes a trigger on Contact, and that causes a trigger on Task, ALL of those triggers share the SAME transaction's governor limits. This cascade effect is the #1 cause of limit failures in enterprise orgs.

Optimization techniques:

  1. Lazy loading โ€” Don't query data you might not need
  2. Selective queries โ€” Use indexed fields in WHERE clauses
  3. Query once, Map forever โ€” Build Maps from SOQL, then reference in loops
  4. Bulk collect โ€” Accumulate records to update, then DML once
  5. Short-circuit โ€” Exit early if no records meet your criteria

๐Ÿ’ป Code Example

codeTap to expand โ›ถ
1// Bulkification โ€” The Complete Pattern
2
3public class BulkificationMastery {
4
5 // โŒ NON-BULKIFIED (fails in production)
6 public static void antiPattern(List<Opportunity> opps) {
7 for (Opportunity opp : opps) {
8 // SOQL in loop โ€” 200 records = 200 queries (limit is 100!)
9 Account acc = [SELECT Name, Industry FROM Account WHERE Id = :opp.AccountId];
10
11 // DML in loop โ€” 200 records = 200 DML statements (limit is 150!)
12 opp.Description = 'Account: ' + acc.Name;
13 update opp;
14
15 // Callout in loop โ€” not even possible (must be after DML)
16 }
17 // With 200 records: 200 SOQL + 200 DML = GOVERNOR LIMIT EXCEPTION
18 }
19
20 // โœ… BULKIFIED (production-ready)
21 public static void bestPractice(List<Opportunity> opps) {
22 // Step 1: COLLECT โ€” Gather all Account IDs
23 Set<Id> accountIds = new Set<Id>();
24 for (Opportunity opp : opps) {
25 if (opp.AccountId != null) {
26 accountIds.add(opp.AccountId);
27 }
28 }
29
30 // Step 2: QUERY ONCE โ€” Single SOQL for all accounts
31 // Step 3: MAP โ€” Build Map for O(1) lookups
32 Map<Id, Account> accountMap = new Map<Id, Account>(
33 [SELECT Id, Name, Industry FROM Account WHERE Id IN :accountIds]
34 );
35 // Only 1 SOQL query used, regardless of how many opportunities
36
37 // Step 4: PROCESS โ€” Apply business logic using the Map
38 List<Opportunity> oppsToUpdate = new List<Opportunity>();
39 for (Opportunity opp : opps) {
40 Account acc = accountMap.get(opp.AccountId);
41 if (acc != null) {
42 opp.Description = 'Account: ' + acc.Name;
43 oppsToUpdate.add(opp);
44 }
45 }
46
47 // Step 5: DML ONCE โ€” Single update for all modified records
48 if (!oppsToUpdate.isEmpty()) {
49 update oppsToUpdate;
50 }
51 // Total: 1 SOQL + 1 DML, regardless of record count
52 }
53
54 // Advanced: Handling multiple related objects
55 public static void complexBulkification(List<Opportunity> opps) {
56 // Collect ALL needed IDs upfront
57 Set<Id> accountIds = new Set<Id>();
58 Set<Id> ownerIds = new Set<Id>();
59
60 for (Opportunity opp : opps) {
61 accountIds.add(opp.AccountId);
62 ownerIds.add(opp.OwnerId);
63 }
64
65 // Query related data in PARALLEL (2 queries, not 2N)
66 Map<Id, Account> accounts = new Map<Id, Account>(
67 [SELECT Id, Name, Industry, OwnerId FROM Account WHERE Id IN :accountIds]
68 );
69 Map<Id, User> owners = new Map<Id, User>(
70 [SELECT Id, Name, Email FROM User WHERE Id IN :ownerIds]
71 );
72
73 // Process with O(1) lookups
74 List<Task> tasksToCreate = new List<Task>();
75 for (Opportunity opp : opps) {
76 if (opp.Amount > 100000) {
77 Account acc = accounts.get(opp.AccountId);
78 User owner = owners.get(opp.OwnerId);
79
80 tasksToCreate.add(new Task(
81 WhatId = opp.Id,
82 OwnerId = opp.OwnerId,
83 Subject = 'High-value opp: ' + acc?.Name,
84 Description = 'Assigned to: ' + owner?.Name,
85 ActivityDate = Date.today().addDays(7)
86 ));
87 }
88 }
89
90 if (!tasksToCreate.isEmpty()) {
91 insert tasksToCreate;
92 }
93 // Total: 2 SOQL + 1 DML for ANY number of records
94 }
95
96 // Monitoring governor limit consumption
97 public static void monitorLimits() {
98 System.debug('=== Governor Limit Usage ===');
99 System.debug('SOQL: ' + Limits.getQueries() + '/' + Limits.getLimitQueries());
100 System.debug('DML: ' + Limits.getDmlStatements() + '/' + Limits.getLimitDmlStatements());
101 System.debug('DML Rows: ' + Limits.getDmlRows() + '/' + Limits.getLimitDmlRows());
102 System.debug('CPU: ' + Limits.getCpuTime() + 'ms/' + Limits.getLimitCpuTime() + 'ms');
103 System.debug('Heap: ' + Limits.getHeapSize() + '/' + Limits.getLimitHeapSize());
104 System.debug('SOQL Rows: ' + Limits.getQueryRows() + '/' + Limits.getLimitQueryRows());
105 }
106
107 // Preventing trigger recursion
108 public class RecursionGuard {
109 private static Set<Id> processedIds = new Set<Id>();
110
111 public static Boolean hasProcessed(Id recordId) {
112 return processedIds.contains(recordId);
113 }
114
115 public static void markProcessed(Id recordId) {
116 processedIds.add(recordId);
117 }
118
119 public static void markProcessed(Set<Id> recordIds) {
120 processedIds.addAll(recordIds);
121 }
122
123 public static void reset() {
124 processedIds.clear();
125 }
126 }
127}

๐Ÿ‹๏ธ Practice Exercise

Bulkification & Limits Exercises:

  1. Take a non-bulkified trigger and refactor it using the Collectโ†’Queryโ†’Mapโ†’Processโ†’DML pattern
  2. Write a trigger that handles 200 records while using only 2 SOQL queries and 1 DML statement
  3. Create a stress test that inserts 200 records via Data Loader and verify your trigger handles it
  4. Write a debugging utility that logs governor limit consumption at key points in your code
  5. Implement a recursion guard that prevents triggers from re-processing the same records
  6. Refactor a trigger cascade (Accountโ†’Contactโ†’Case) to stay within limits
  7. Write code that intentionally exceeds each governor limit and handle the exceptions gracefully
  8. Design a batch process that handles 1 million records without hitting any limits
  9. Create a before-trigger that validates records against complex criteria using only 1 SOQL query
  10. Benchmark your code: measure SOQL, DML, and CPU usage for processing 1, 50, and 200 records

โš ๏ธ Common Mistakes

  • Writing code that works for 1 record and assuming it works for 200 โ€” always test with bulk data (200 records minimum)

  • Not recognizing cascading triggers โ€” a trigger on Account that updates Contacts fires the Contact trigger, sharing the same limits

  • Using Limits class for flow control โ€” checking limits to decide whether to query is an anti-pattern; it means your code isn't properly bulkified

  • Not accounting for existing automation โ€” your trigger shares limits with Flows, Process Builders, and other triggers on the same object

  • Ignoring CPU time limits โ€” even with perfect SOQL/DML optimization, complex logic in large loops can exceed the 10-second CPU limit

๐Ÿ’ผ Interview Questions

๐ŸŽค Mock Interview

Mock interview is powered by AI for Bulkification & Governor Limits Mastery. Login to unlock this feature.