Bulkification is writing trigger code that handles a batch of records — up to 200 in one invocation — without doing per-record SOQL queries, DML statements, or callouts. Salesforce triggers always receive a collection. UI saves give you one record; Data Loader, the API, and async jobs hand you 200. Code that wasn’t written with bulk in mind blows governor limits the moment a real workload hits.
The two rules
- No SOQL inside a
forloop. Collect Ids first, query once, work from the resulting map. - No DML inside a
forloop. Build a list, then callupdateorinsertonce outside the loop.
Non-bulk vs bulk side-by-side
// BAD — fails on 101 records
trigger CaseBad on Case (after update) {
for (Case c : Trigger.new) {
Account a = [SELECT Id, Open_Cases__c FROM Account WHERE Id = :c.AccountId]; // SOQL in loop
a.Open_Cases__c = a.Open_Cases__c + 1;
update a; // DML in loop
}
}
// GOOD — handles 200 records in 2 queries / 1 DML
trigger CaseGood on Case (after update) {
Set<Id> acctIds = new Set<Id>();
for (Case c : Trigger.new) {
if (c.AccountId != null) acctIds.add(c.AccountId);
}
Map<Id, Account> byId = new Map<Id, Account>([
SELECT Id, Open_Cases__c FROM Account WHERE Id IN :acctIds
]);
for (Case c : Trigger.new) {
Account a = byId.get(c.AccountId);
if (a != null) a.Open_Cases__c = (a.Open_Cases__c == null ? 1 : a.Open_Cases__c + 1);
}
update byId.values();
}
Governor limits you’re protecting against
| Limit | Sync per transaction |
|---|---|
| Total SOQL queries | 100 |
| Total DML statements | 150 |
| Records returned by SOQL | 50,000 |
| CPU time | 10,000 ms |
| Heap size | 6 MB |
A non-bulk trigger that issues one SOQL per record fails at record 101. With Data Loader’s default batch of 200, that’s every real-world load.
Bulk-safe patterns to memorize
- Collect IDs into a
Set<Id>before any query. - Use a
Map<Id, SObject>for O(1) lookup back into the records you queried. - Build a list, DML once. Whether
insert,update, ordelete, never do it inside the loop. - Filter before querying. A
Set<Id>deduplicates for free, but skip the query entirely if the set is empty. - Use parent–child relationship queries to fetch related records in one round trip when possible.
Common interview follow-ups
- How many records arrive per trigger invocation? — Up to 200. Data Loader’s default batch is 200; the API caps a single call at 200 as well.
- Does Salesforce enforce bulkification? — Not directly, but governor limits make non-bulk code fail under any realistic load.
- What if I need to do a callout per record? — You can’t from a trigger directly. Pass IDs to a Queueable that does the callouts (still bulk-aware, batching by HTTP timeouts).
Verified against: Apex Developer Guide — Bulk Triggers, Execution Governors and Limits. Last reviewed 2026-05-17.