7.4 Governor Limits & Transaction Boundaries
Key Takeaways
- Governor limits are platform-enforced multi-tenant protections that cap work per Apex transaction so one tenant cannot starve others
- Key synchronous limits candidates memorize: 100 SOQL queries, 50,000 SOQL rows, 150 DML statements, 10,000 DML rows, 10,000 ms CPU, 6 MB heap, 100 callouts—verify current Apex Developer Guide if Salesforce revises numbers
- Async Apex (Queueable, Batch, future, scheduled) often receives higher limits for some resources; still design bulk-safe code
- A transaction boundary is one atomic unit of work—request, trigger cascade, or async job execution—limits reset at each new transaction
- LimitException ends the transaction when a limit is exceeded; bulkification (collections, single query/DML patterns) is the primary defense
7.4 Governor Limits & Transaction Boundaries
Quick Answer: Salesforce enforces governor limits on each Apex transaction so multi-tenant neighbors stay safe. For synchronous work, memorize the classic ceilings: 100 SOQL queries, 50,000 SOQL rows, 150 DML statements, 10,000 DML rows, 10,000 ms CPU, 6 MB heap, 100 callouts (plus related limits). Exceeding a limit throws
LimitExceptionand aborts the unit of work. Bulkification keeps real triggers under these caps. Always treat published numbers as platform rules worth re-checking in the current Apex Developer Guide if Salesforce revises them.
This section explains why sections 7.1–7.3 obsess over one-query / one-DML patterns. Limits are not trivia—they are the operating system of Lightning Platform.
Why Limits Exist (Multi-Tenant Protection)
Salesforce runs many customers on shared infrastructure. Without per-transaction caps, one runaway trigger (SOQL in a loop over millions of rows, infinite recursion, huge heap) could degrade the instance. Governors are hard platform rules, not style suggestions. Code that “works for one record in the UI” can fail on a Data Loader insert of 200 rows—exactly the scenario exams simulate.
Key Synchronous Per-Transaction Limits
Candidates should know these synchronous figures as commonly tested baselines. If Salesforce updates documentation, prefer the current Apex Developer Guide—the exam and this guide use the long-standing teaching set below.
| Resource | Typical synchronous limit | What burns it |
|---|---|---|
| Total SOQL queries | 100 | Each SOQL statement (including those in loops!) |
| Total records retrieved by SOQL | 50,000 | Rows returned across queries (including subquery rows) |
| Total DML statements | 150 | Each insert/update/upsert/delete/undelete statement |
| Total records processed by DML | 10,000 | Sum of rows in DML operations |
| Maximum CPU time | 10,000 ms | Apex CPU (not pure wait on callouts the same way) |
| Maximum heap size | 6 MB | In-memory collections, strings, queried rows |
| Maximum callouts | 100 | HTTP callouts per transaction |
Related limits you should recognize by name even if exact secondary numbers vary by release/context:
- SOSL queries per transaction (much lower than SOQL count)
- Aggregate query row implications toward retrieval totals
- Email invocations, push notifications, future calls, Queueable chain depth
- Batch Apex scope sizes and per-execute limits
Teaching mnemonic: Queries (100) and DML statements (150) are about how many times you talk to the database. Rows (50k / 10k) are about how much data you move. CPU and heap are about how hard Apex works in memory.
Reading limits in code
System.debug('SOQL queries used: ' + Limits.getQueries() + ' / ' + Limits.getLimitQueries());
System.debug('DML statements used: ' + Limits.getDmlStatements() + ' / ' + Limits.getLimitDmlStatements());
System.debug('CPU time: ' + Limits.getCpuTime() + ' / ' + Limits.getLimitCpuTime());
System.debug('Heap: ' + Limits.getHeapSize() + ' / ' + Limits.getLimitHeapSize());
Use Limits methods in diagnostics—not as a substitute for bulk design. Polling limits in production paths is rare; designing so you stay far under caps is the goal.
Async Differences (High Level)
Asynchronous executions—@future, Queueable, Batch Apex execute, Schedulable jobs—often receive higher limits for some resources (for example, higher SOQL row or heap ceilings in async contexts). That does not mean you can SOQL-in-a-loop safely. Batch Apex is the platform tool for millions of rows: each execute is its own transaction with its own governors, scoped by batch size.
| Context | Mental model |
|---|---|
| Synchronous (UI, Lightning controller, trigger from interactive DML) | Tightest common teaching limits (table above) |
| Async single transaction (future/Queueable) | Often more headroom; still one transaction’s caps |
| Batch execute | Chunked transactions; design per-scope bulk patterns |
Callouts + DML ordering: You cannot make a callout after uncommitted DML in the same transaction without careful patterns; async is often used to separate callout work. Limits and order-of-operations interact—another reason transaction boundaries matter.
Transaction Boundaries
A transaction is the atomic unit where:
- Governors accumulate from zero to their caps
- Successful completion commits DML together
- Failure / unhandled exception / LimitException rolls back work for that transaction (unless handled with partial patterns/savepoints carefully)
Examples of boundaries:
- A Lightning action that runs Apex and finishes.
- A trigger-bearing insert of 200 Accounts from the API—one transaction for that batch, including all automation (before/after triggers, flows in the same invocation path, etc., as order of execution defines).
- Each Queueable
executemethod run. - Each Batch Apex
executechunk.
Critical insight: Static variables reset between transactions but persist across trigger recursions within the same transaction—used for recursion control, not for long-term storage. Limits also accumulate across those recursive trigger firings in the same transaction. Bulkifying reduces statements; static flags stop infinite re-entry.
public class AccountTriggerHandler {
private static Boolean ran = false;
public static void afterUpdate(List<Account> news) {
if (ran) return;
ran = true;
// safe bulk work once per transaction
}
}
LimitException
When code exceeds a governor, the platform throws a System.LimitException (message indicates which limit). You generally cannot catch LimitException to keep going usefully—the transaction is in a failed state for continuing the same work. Design to prevent the exception:
- Fewer SOQL/DML statements via collections
- Selective filters and LIMITs when full tables are unnecessary
- Batch/Queueable for volume beyond interactive transactions
- Lean heap: avoid holding giant strings/lists longer than needed
Exam questions often show a loop with SOQL and ask what fails first: Too many SOQL queries: 101 is the iconic error string pattern.
How Bulkification Avoids Limits
| Anti-pattern | Limit hit | Fix |
|---|---|---|
SOQL per Trigger.new row | SOQL queries 100 | One query with IN :idSet |
| DML per row | DML statements 150 | One update list |
| Querying huge unbounded tables into a list | Heap 6 MB / rows 50k | SOQL-for loop, selective WHERE, Batch |
| Nested loops with expensive string work on large lists | CPU 10s | Maps, fewer iterations, move heavy work async |
| Callout per record in a loop of 200 | Callouts 100 | Aggregate callouts, bulk APIs, Queueable chaining carefully |
Worked mini-scenario
Data Loader inserts 200 Contacts. Trigger runs once with Trigger.new.size() == 200.
- Bad: 200 SOQL + 200 DML → fails SOQL or DML statement limits.
- Good: 1 SOQL for related Accounts + 1 DML update on Contacts → uses 2 database statements for statements-count purposes (plus whatever else automation adds), well under 100/150.
Remember: other automation (Flow, rollups, package code) shares the same transaction limits. Defensive bulk code leaves headroom.
Practical Exam Strategy
- Count statements in the worst-case batch (usually 200 records).
- Spot SOQL/DML inside for immediately—almost always wrong.
- Prefer maps and sets in answers that scale.
- Know when to recommend Batch Apex vs a synchronous trigger solution.
- Treat limit numbers as platform-enforced multi-tenant protections, and note that official docs win if Salesforce revises a figure between your study guide and exam day.
Connecting 7.1–7.4
- SOQL skills load data efficiently (relationship queries, binds, SOQL-for loops).
- SOSL skills search text without fake multi-object SOQL.
- DML skills persist changes with bulk lists and correct allOrNone behavior.
- Governors are the scoring rubric the platform applies to all of the above.
If you can look at any Apex snippet and predict which governor breaks first—and rewrite it to collect, query once, DML once—you are operating at Platform Developer I level for data access.
Which set correctly lists commonly tested synchronous per-transaction Apex governor limits?
A trigger performs one SOQL query inside a for loop for each of 200 records in Trigger.new. What is the most likely outcome?
What is an Apex transaction boundary in the context of governor limits?