Most Salesforce apex outages trace back to one of five limits. Knowing which they are — and which patterns hit them — is half the battle.
The “front five” you’ll meet in production
| Limit | Synchronous cap | Typical cause |
|---|---|---|
| SOQL queries per transaction | 100 | A SELECT inside a for loop on Trigger.new |
| DML statements per transaction | 150 | An update inside a loop, or per-record save |
| Total DML rows per transaction | 10,000 | Mass-update batch processed in a single transaction |
| Apex CPU time | 10,000 ms (10 s) | Nested loops over large collections, heavy Map joins |
| Heap size | 6 MB | Holding 50k SObjects in memory for processing |
1. SOQL queries (101 / “too many SOQL queries”)
The classic bulkification failure:
// BAD — fires N queries when N accounts come through
for (Account a : Trigger.new) {
a.Top_Contact__c = [SELECT Id FROM Contact
WHERE AccountId = :a.Id LIMIT 1].Id;
}
Fix: query once, map by parent ID, look up in the loop.
Map<Id, Contact> firstByAcct = new Map<Id, Contact>();
for (Contact c : [SELECT Id, AccountId FROM Contact
WHERE AccountId IN :Trigger.newMap.keySet()
ORDER BY CreatedDate]) {
if (!firstByAcct.containsKey(c.AccountId)) {
firstByAcct.put(c.AccountId, c);
}
}
for (Account a : Trigger.new) {
Contact c = firstByAcct.get(a.Id);
if (c != null) a.Top_Contact__c = c.Id;
}
2. DML statements (151 / “too many DML statements”)
// BAD
for (Account a : accs) update a;
// GOOD
update accs;
Even when each call is small, the platform counts the statement, not the row count.
3. DML rows (10,001+ records)
Mass-data jobs that try to save more than 10,000 records in one transaction throw Too many DML rows. The fix is Batch Apex — each execute() call is its own transaction with its own budget.
4. CPU time exceeded
This one’s sneaky because there’s no single API call to blame. It accumulates from every operation in pure Apex (loops, string ops, JSON parse, sort, math).
Top causes:
- Nested loops over
Trigger.new× related records when aMap<Id, ...>lookup would do it in O(N) - Heavy
JSON.deserializeof large payloads - Repeated
instanceof/getDescribecalls
Diagnose with Limits.getCpuTime() printed at checkpoints.
5. Heap size
Storing tens of thousands of fat SObjects (long text fields, formulas) breaches 6 MB fast. The fix:
// Streams 200 rows at a time, garbage-collecting each batch
for (Account a : [SELECT Id, Name FROM Account WHERE Active__c = true]) {
process(a);
}
A for loop over a SOQL query expression — not a pre-fetched List — keeps heap flat.
The “silent five” that hurt on integrations
| Limit | Cap | Where it bites |
|---|---|---|
| Callouts per transaction | 100 | Loop calling an external API per record |
| Cumulative callout timeout | 120 s | A single slow endpoint stacked many times |
@future calls | 50 | Fire-and-forget per record in a trigger |
| Queueable depth (chained) | 5 (sync), unlimited (async) | Recursive job chains |
| Email invocations | 10 (single), 10 (mass) | Notification per record |
What interviewers are really looking for
Knowing the numbers is table stakes. The signal is whether you can match each limit to the anti-pattern that triggers it — SELECT in a loop hits the 100 SOQL cap; per-record DML hits 150 statements; cross-joining Trigger.new against related records hits 10s CPU. Wrap up with bulkification, SOQL aggregation, and async deferral as the structural fixes.
Verified against: Apex Developer Guide — Execution Governors and Limits. Last reviewed 2026-05-17 for Spring ‘26 release.