Skip to main content

SF-0340 · Concept · Medium

What are bulk triggers? or How to bulkify triggers?

✓ Verified by Vikas Singhal · Last reviewed 5/17/2026 · Updated for Spring '26

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

  1. No SOQL inside a for loop. Collect Ids first, query once, work from the resulting map.
  2. No DML inside a for loop. Build a list, then call update or insert once 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

LimitSync per transaction
Total SOQL queries100
Total DML statements150
Records returned by SOQL50,000
CPU time10,000 ms
Heap size6 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, or delete, 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.