Governor limits are per-transaction caps that Salesforce enforces on Apex code — limits on how many SOQL queries, DML statements, CPU milliseconds, heap bytes and callouts a single execution can consume. Because Salesforce is a multi-tenant platform where thousands of organisations share the same infrastructure, these limits exist to keep one customer's runaway code from degrading performance for everyone else.
They are non-negotiable. When your code crosses a limit, the Apex runtime throws an uncatchable LimitException and rolls back the entire transaction. This guide covers the per-transaction limits that matter most in 2026, why they exist, what happens when you breach one, and the bulkification patterns that keep your code comfortably within them.
Why Salesforce enforces governor limits
Salesforce runs on a shared, multi-tenant architecture: your org, your competitor's org and thousands of others all run on the same infrastructure and the same database. Without hard limits, a single inefficient trigger looping over millions of rows could starve every other tenant of CPU and database time.
Governor limits solve this by bounding what one Apex transaction can do. They are reset at the start of every transaction, so well-architected code that runs one query for many records sails through, while code that queries inside a loop quickly runs out of headroom. In short, the limits push you toward writing efficient, bulk-safe Apex.
Salesforce groups these constraints into a few families: per-transaction Apex limits (the ones you hit most often), static Apex limits, size-specific limits, and limits on email, push notifications and asynchronous Apex. This article focuses on the per-transaction limits, which are where almost every real-world LimitException comes from.
The key per-transaction governor limits
The table below lists the limits you are most likely to encounter. Asynchronous figures apply to Batch, Queueable, @future and Scheduled Apex.
| Per-transaction limit | Synchronous | Asynchronous |
|---|---|---|
| Total SOQL queries issued | 100 | 200 |
| Total records retrieved by SOQL queries | 50,000 | 50,000 |
| Total SOSL queries issued | 20 | 20 |
| Records retrieved by a single SOSL query | 2,000 | 2,000 |
| Total DML statements issued | 150 | 150 |
Records processed by DML / Database operations |
10,000 | 10,000 |
| Total callouts (HTTP requests / web services) | 100 | 100 |
| Maximum cumulative callout timeout | 120 s | 120 s |
| Maximum CPU time on the Salesforce servers | 10,000 ms | 60,000 ms |
| Maximum execution time per Apex transaction | 10 min | 10 min |
| Total heap size | 6 MB | 12 MB |
@future method calls per transaction |
50 | 50 |
Jobs queued with System.enqueueJob |
50 | 1 (chaining) |
A few clarifications that trip people up:
- The 50,000-row SOQL limit counts the total rows your transaction reads, not rows per query — ten queries that each return 6,000 rows will breach it.
- CPU time measures time executing on Salesforce's application servers; it does not include time waiting on the database or on callouts. Heavy loops, JSON parsing and complex formula evaluation are the usual culprits.
- Heap is the memory your transaction holds at once; querying huge collections into memory is the fastest way to hit 6 MB.
- These numbers have been stable across recent releases, but they can change by edition and API version. Always confirm the current figures in Salesforce's official Execution Governors and Limits documentation.
Synchronous vs asynchronous limits
Asynchronous Apex — @future methods, Queueable, Batch and Scheduled Apex — runs in a separate transaction and gets more generous limits, because it isn't tied to a user waiting on a screen. The biggest differences are 200 SOQL queries instead of 100, 60 seconds of CPU instead of 10, and 12 MB of heap instead of 6 MB. The DML statement (150) and total SOQL row (50,000) limits stay the same.
Batch Apex deserves special mention. Its start method can return a Database.QueryLocator that streams up to 50 million records, and the framework hands them to your execute method in chunks (default 200, configurable up to 2,000). Crucially, governor limits are reset for each chunk, which is exactly what makes Batch Apex the standard tool for processing large data volumes without tripping the per-transaction caps.
There are also limits on how much async you can run: across a rolling 24-hour window an org can run the greater of 250,000 asynchronous executions or (number of user licences × 200).
What happens when you hit a governor limit
When Apex exceeds a per-transaction limit, the runtime throws a System.LimitException — for example, System.LimitException: Too many SOQL queries: 101. This exception is uncatchable: a try/catch block will not swallow it, and the entire transaction is rolled back as if it never ran. Any DML already performed in that transaction is undone.
Because you cannot catch it, the only real strategy is to avoid it. The Limits class lets you inspect consumption at runtime, so you can short-circuit before you breach a cap:
// Compare current usage against the ceiling for this transaction
System.debug('SOQL queries: ' + Limits.getQueries() + ' of ' + Limits.getLimitQueries());
System.debug('DML rows: ' + Limits.getDmlRows() + ' of ' + Limits.getLimitDmlRows());
System.debug('CPU time (ms): ' + Limits.getCpuTime() + ' of ' + Limits.getLimitCpuTime());
// Defensive guard: defer work asynchronously before hitting the wall
if (Limits.getQueries() >= Limits.getLimitQueries() - 1) {
// enqueue the remaining work instead of issuing another query
System.enqueueJob(new RemainingWorkJob(recordsToProcess));
}Bulkification: the number-one way to avoid limits
The single most common cause of LimitException is placing SOQL or DML inside a loop. A trigger that fires on 200 records and runs one query per record issues 200 queries — instantly past the synchronous limit of 100. Bulkification means writing code that processes a whole collection with a fixed, small number of queries and DML statements, no matter how many records arrive.
The anti-pattern below queries and updates inside the loop and fails the moment volumes grow:
// ANTI-PATTERN: SOQL and DML inside a loop -> fails on large data volumes
trigger AccountTrigger on Account (after update) {
for (Account acc : Trigger.new) {
// One SOQL query per Account -> "Too many SOQL queries: 101"
List<Contact> contacts = [SELECT Id FROM Contact WHERE AccountId = :acc.Id];
for (Contact c : contacts) {
c.Description = 'Parent account updated';
update c; // One DML per Contact -> "Too many DML statements: 151"
}
}
}The fix is to query once for every parent, collect the changes into a list, and issue a single DML at the end:
// BULKIFIED: one query for all parents, one DML for all children
trigger AccountTrigger on Account (after update) {
// Single SOQL: every Contact for every Account in this batch
List<Contact> toUpdate = [
SELECT Id, Description
FROM Contact
WHERE AccountId IN :Trigger.newMap.keySet()
];
for (Contact c : toUpdate) {
c.Description = 'Parent account updated';
}
// Single DML for the whole collection
if (!toUpdate.isEmpty()) {
update toUpdate;
}
}Whether it runs against 1 record or 10,000, this version uses exactly one SOQL query and one DML statement. The core rules of bulkification:
- Never put SOQL or DML inside a
forloop. - Query once with
IN :collection(or a parent key set) and work from the returned list. - Accumulate records in a
ListorMapand run DML on the whole collection in one call. - Use
Map<Id, SObject>to relate child records back to parents efficiently — see our guides on Map methods in Salesforce and how to run a SOQL query in Salesforce. - Assume every trigger and class will one day run against 200+ records.
Strategies for high-volume data
When a single transaction genuinely can't fit the work inside the limits, move it off the synchronous path:
- Batch Apex — process up to 50 million records in resettable chunks; ideal for nightly data fixes and large migrations. Use
Database.Statefulwhen you need to carry counters or totals across chunks. - Queueable Apex — chain jobs for sequential async work with full access to non-primitive types; enqueue with
System.enqueueJob. @futuremethods — lightweight fire-and-forget async for callouts or simple offloading (primitive arguments only).- Bulk API / Data Loader — load and update millions of records from outside the platform without consuming Apex limits at all; pair this with managing large data volumes using skinny tables and indexes.
- Platform Events — decouple processing so producers and consumers each run within their own limit budget.
Choosing the right tool is an architecture decision as much as a coding one: the goal is to keep every transaction comfortably inside its caps while still meeting throughput requirements.
Frequently Asked Questions
What happens when you hit a governor limit in Salesforce?
Apex throws a System.LimitException (for example, Too many SOQL queries: 101) and immediately rolls back the entire transaction — any DML performed earlier in that transaction is undone. The exception is uncatchable, so a try/catch will not stop it. The only reliable fix is to avoid breaching the limit in the first place.
How do I avoid Salesforce governor limits?
Bulkify your code. Never run SOQL or DML inside a loop; instead query once for all records using IN :collection, build up a List or Map, and issue a single DML for the whole collection. Assume every trigger and class will eventually process 200 or more records, and move heavy or high-volume work to Batch or Queueable Apex.
What is the difference between synchronous and asynchronous limits?
Asynchronous Apex (Batch, Queueable, @future, Scheduled) gets higher limits because no user is waiting on it: 200 SOQL queries instead of 100, 60,000 ms of CPU instead of 10,000 ms, and 12 MB of heap instead of 6 MB. The DML statement (150) and SOQL row (50,000) limits stay the same, and Batch Apex additionally resets limits for each chunk of records it processes.
How many SOQL queries and DML statements can one transaction run?
In a synchronous transaction you can issue up to 100 SOQL queries (200 asynchronous) and 150 DML statements. Across all queries you can retrieve at most 50,000 rows, and a single DML operation can process at most 10,000 records. SOSL is capped at 20 queries per transaction.
Can Salesforce governor limits be increased?
The standard per-transaction limits are fixed and cannot be raised through a support ticket — they protect the shared multi-tenant platform. A small number of org-wide, non-per-transaction limits (such as concurrent API requests or certain batch limits) can occasionally be reviewed by Salesforce, but the right answer is almost always to re-architect: bulkify, use asynchronous Apex, or offload bulk loads to the Bulk API.
How can I monitor governor limit usage at runtime?
Use the built-in Limits class. Methods such as Limits.getQueries() / Limits.getLimitQueries(), Limits.getDmlRows() / Limits.getLimitDmlRows() and Limits.getCpuTime() / Limits.getLimitCpuTime() return current usage versus the ceiling, so you can log consumption or defensively defer work to an async job before you hit a cap. The developer console and debug logs also show a cumulative limits summary for every transaction.
Build limit-safe Salesforce solutions with MicroPyramid
For over 12 years MicroPyramid has built and maintained Salesforce solutions across 50+ projects for startups and enterprises in the US, UK, Australia, Canada and beyond. Our Apex developers design triggers, batch jobs and integrations that stay well inside governor limits at scale — and we routinely refactor legacy orgs whose code breaks the moment data volumes grow.
If you need Salesforce customisation, Apex development, or an audit of code that keeps hitting limits, explore our Salesforce consulting and development services.