Governor Limits & Bulkification

The limits every interviewer asks about and the before/after code patterns that keep you under them.

Developer 3 min readGovernor limitsBulkificationPerformance

Salesforce is multi-tenant, so every transaction gets a fixed budget. Exceed it and you get an uncatchable System.LimitException — the whole transaction rolls back.

Per-transaction limits (most asked)

LimitSynchronousAsynchronous
SOQL queries100200
Records retrieved by SOQL50,00050,000
DML statements150150
Records processed by DML10,00010,000
CPU time10,000 ms60,000 ms
Heap size6 MB12 MB
Callouts100100
SOSL queries2020
@future calls500 in batch/future

Check usage at runtime with the Limits class: Limits.getQueries() / Limits.getLimitQueries().

Triggers receive up to 200 records at a time

A single DML of 1,000 records fires your trigger 5 times (chunks of 200) in the same transaction, and all chunks share one set of limits. Code that's fine for 1 record explodes in bulk. (Data Loader / the Bulk API send batches; each batch is its own transaction.)

Anti-pattern: SOQL / DML inside a loop

apex
// ❌ 1 query + 1 DML per record → fails at the 101st record
for (Opportunity opp : Trigger.new) {
    Account acc = [SELECT Id, Type FROM Account WHERE Id = :opp.AccountId];
    acc.Type = 'Customer';
    update acc;
}

Bulkified version

apex
// ✅ 0 queries, 1 DML for any number of records
Set<Id> accountIds = new Set<Id>();
for (Opportunity opp : Trigger.new) {
    Opportunity old = Trigger.oldMap.get(opp.Id);
    if (opp.StageName == 'Closed Won' && old.StageName != 'Closed Won' && opp.AccountId != null) {
        accountIds.add(opp.AccountId);
    }
}
List<Account> toUpdate = new List<Account>();
for (Id accId : accountIds) {
    toUpdate.add(new Account(Id = accId, Type = 'Customer'));
}
if (!toUpdate.isEmpty()) update toUpdate;

The bulkification checklist

  1. Collect keys (Ids, emails, names) into a Set in one loop.
  2. Query once with WHERE … IN :set, put results in a Map.
  3. Loop again and look up from the Map.
  4. Collect records to change into a List.
  5. One DML outside the loop (and skip it when the list is empty).

Other ways to stay under limits

  • Query only the fields you need; use WHERE filters on indexed fields (Id, Name, lookups, External IDs).
  • Use SOQL for loops for big result sets (for (List<Account> chunk : [SELECT …])) to reduce heap.
  • Move heavy work to async Apex (Queueable / Batch) for higher limits.
  • Avoid recursion — a trigger updating its own object re-fires itself.

Interview questions

  1. Your trigger works in the UI but fails during a 5,000-row data load — why? → It isn't bulkified (SOQL/DML in loops).
  2. How many records does a trigger get per invocation? → Up to 200.
  3. Can you catch a governor limit exception? → No — prevent it.
  4. How do you check how many queries you've used? → Limits.getQueries().

Practice this