Salesforce governor limits are enforced per transaction to protect the shared multitenant platform from any single customer's code monopolising resources. They are not bugs, and they cannot be negotiated away. But hitting them in production is always a code quality problem, not a platform limitation problem. Every governor limit violation that surfaces in production was detectable — in the sandbox, in static analysis, in a code review — before it went live. Understanding the most common Salesforce governor limits and the patterns that cause them turns a production incident waiting to happen into a design standard your team follows from day one.
The Key Governor Limits You Need to Know
| Limit | Synchronous | Asynchronous | Most Common Violation |
|---|---|---|---|
| SOQL queries | 100 | 200 | SOQL inside a loop |
| DML statements | 150 | 150 | DML inside a loop |
| Records per DML | 10,000 | 10,000 | Batch size misconfiguration |
| CPU time | 10,000ms | 60,000ms | Complex nested loops, string manipulation |
| Heap size | 6 MB | 12 MB | Large SObject collections in memory |
| Callouts per transaction | 100 | 100 | Callouts in loops |
| Future methods per transaction | 50 | — | Spawning @future inside loops |
The Cardinal Sin: SOQL and DML Inside Loops
The SOQL-in-a-loop violation is the most widespread governor limit problem in Salesforce Apex, and it surfaces almost exclusively in production under bulk load conditions. The pattern looks like this:
A trigger fires on Account update. The trigger iterates over Trigger.new and, for each Account, queries all related Contacts: SELECT Id FROM Contact WHERE AccountId = :acc.Id. In a developer sandbox with one record being updated at a time, this fires once — one SOQL query. In production, when an integration batch-updates 100 Accounts simultaneously, the same trigger fires 100 SOQL queries in a single transaction, immediately hitting the 100-query limit and throwing a System.LimitException.
The fix — called bulkification — is to collect all Account IDs before the loop, run a single SOQL query for all related Contacts using an IN clause, store the results in a Map, and then reference the Map inside the loop:
Bulkified pattern: Collect IDs → one SOQL query → Map<Id, List<SObject>> → iterate Map in loop → collect DML in List → one DML after loop
The same principle applies to DML. Never call update record inside a loop. Collect all records that need updating into a List, and call update recordList once after the loop exits.
Asynchronous Processing as a Governor Limit Strategy
Some operations genuinely require more resources than synchronous governor limits allow. The answer is not to try to optimise within synchronous limits — it is to move the operation to an asynchronous context where higher limits apply and the platform handles scheduling.
The four async processing frameworks, and when to use each:
- Batch Apex — for processing large volumes of records in chunks. Each batch chunk gets its own synchronous transaction with fresh limits. The
Database.Batchableinterface splits your dataset into chunks of up to 2,000 records and processes each chunk independently. Right for nightly data maintenance, large-scale field updates, and retention automation. - Queueable Apex — for chained asynchronous processing. Unlike @future methods, Queueable jobs can be chained (one job enqueues the next), can receive non-primitive arguments (including SObject collections), and can be monitored via AsyncApexJob. Right for multi-step async workflows.
- @future methods — for simple fire-and-forget async tasks: callouts from triggers, lightweight background processing. Cannot be chained, cannot accept SObject parameters. Right for simple, single-step async needs.
- Scheduled Apex — for time-based execution: running a batch job nightly, processing queued operations at a specific time. Implements the
Schedulableinterface and is configured via Setup or programmatically.
Flow Automation and Governor Limits
Flow automation interacts with governor limits in ways that are less visible than Apex but equally consequential. Record-triggered flows are subject to the same per-transaction limits as Apex triggers — a flow that queries related records in a loop will hit the SOQL limit just as reliably as an Apex trigger doing the same thing. The difference is that flow violations are harder to detect in advance because flows do not have the same static analysis tooling as Apex.
Two common flow governor limit patterns to audit:
- Get Records elements in loops — every Get Records element in a flow is a SOQL query. A loop that contains a Get Records element will query once per loop iteration. For small loops this is manageable; for loops over large collections this hits limits quickly.
- Multiple triggered flows on the same object — Salesforce now recommends consolidating flows: one record-triggered flow per object per trigger context (before-save or after-save). Multiple flows on the same object each consume limit resources independently, and their combined consumption can exceed limits even when each individual flow looks safe.
Governor limit violations hiding in your org?
SproutEzee gives you production-representative sandbox environments where realistic data volumes surface limit violations before go-live — not after.
See SproutEzee →Frequently Asked Questions
What are the most commonly hit Salesforce governor limits in production?
The most common are: SOQL query limit (100 synchronous) — hit by SOQL inside loops; DML statement limit (150) — hit by DML inside loops; CPU time limit (10,000ms synchronous) — hit by complex automation chains; heap size limit (6 MB synchronous) — hit by large collections in memory; and daily API call limits — hit by integrations making individual calls per record instead of batching.
How do you fix SOQL queries inside loops in Salesforce Apex?
Bulkify your code: collect the set of IDs before the loop, perform a single SOQL query using an IN clause, store results in a Map keyed by ID, and access the Map inside the loop. This reduces N queries to 1 query regardless of batch size. The same pattern applies to DML — collect records in a List and issue one DML after the loop.
Why do governor limits feel worse in production than in sandbox?
Transactions in production process more records simultaneously. A trigger built for single-record updates works fine in sandbox. In production, a bulk integration posting 200 records fires the same trigger with 200 records in one transaction. SOQL-in-a-loop that ran 1 query in sandbox now runs 200, immediately hitting the limit. Sandbox tests rarely reveal this; production always will.
What tools can help identify governor limit issues before production?
The Developer Console's Limits tab during Apex execution, Apex log analysis via the Tooling API, static analysis with PMD's Apex ruleset, and running automated tests in a partial sandbox with realistic data volumes. Realistic sandbox data is the most reliable way to surface limit violations before go-live.
Can Salesforce governor limits be increased or bypassed?
Most cannot be permanently increased — they are a feature of the multitenant architecture. Salesforce support can increase API call daily limits with a business justification. The correct approach is designing code that does not approach existing limits. Asynchronous processing (Batch Apex, Queueable, @future) provides higher limits for operations that do not need to be synchronous.