If you’ve built on Salesforce for even a day, you’ll hit these. If you haven’t yet, you will. But here’s the secret: knowing governor limits isn’t just about avoiding errors. It’s about crafting code that scales, stays performant, and doesn’t crash when your org grows from hundreds of records to millions. Ever had your code running like a boss, then boom, System.LimitException: Too many SOQL queries: 101 flashes in your face? Welcome to the wild world of Salesforce governor limits, irritating but necessary guardrails that keep your org stable and compliant.
What Are Governor Limits in Salesforce?
Imagine your org is in a big apartment building (the Salesforce cloud). Every tenant (org) shares electricity, water, bandwidth, etc. If one flat starts hoarding all resources, everyone else suffers.
That’s where governor limits come in: they’re system-enforced quotas that prevent any single transaction from consuming too much CPU, memory, or database access.
Because Salesforce uses a multi-tenant architecture, these limits are essential. They ensure:
- Fair usage across orgs
- Platform stability and performance
- Encouragement for clean, efficient coding
- Prevention of runaway code (infinite loops, runaway recursion)
When your code tries to exceed a limit, Salesforce cuts it off, throws a runtime exception, and rolls back the transaction.
What’s a Transaction?
In Salesforce, a transaction is a logical unit of work:
- It begins when a trigger, Visualforce controller, API call, or UI action is invoked
- It includes everything your code does, queries, DMLs, loops, logic, callouts
- It ends when everything completes successfully or is rolled back due to an error
All the governor limits we’ll cover reset per transaction. That means every transaction has a fresh “quota” of queries, DMLs, CPU time, etc.
Key Per-Transaction Apex Limits & Code Examples
Here are the most salient limits you’ll bump into daily, with code samples and nuance:
1. SOQL Query Limit
- Limit: 100 SOQL queries in synchronous transactions; 200 in asynchronous.
- Why it matters: If you put a SOQL inside a loop, it multiplies queries. You’ll hit exceptions quickly.
Example:
for (Integer i = 0; i < 101; i++) {
List<Account> accts = [SELECT Id FROM Account];
}
That fails at iteration 101 - “Too many SOQL queries: 101”.
Even if you split into multiple loops:
for (Integer i = 0; i < 40; i++) {
List<Account> accts = [SELECT Id FROM Account];
}
for (Integer i = 0; i < 61; i++) {
List<Contact> ct = [SELECT Id FROM Contact];
}
You still exceed 100 in total (40 + 61 = 101), so you crash.
2. Records Returned via SOQL
- Limit: 50,000 total records returned per transaction across all queries.
- Detail: It doesn’t matter how many queries – the sum of returned rows matters.
- Example:
List<Account> accts = [SELECT Id FROM Account LIMIT 40000];
List<Contact> contacts = [SELECT Id FROM Contact LIMIT 15000];
// 40,000 + 15,000 = 55,000 → exceeds 50,000
Even though you ran just 2 queries, you blew past the row limit.
3. DML Statement Limits
- Limit: 150 DML statements in a single transaction.
- Clarification: This is about how many times you call insert, update, delete, upsert, etc.
- Example:
for (Integer i = 0; i < 155; i++) {
Account acc = new Account(Name = 'Test' + i);
insert acc;
}
You’ll hit Too many DML statements: 151 at iteration 151.
4. DML Rows (Records Processed)
- Limit: 10,000 records processed via DML in one transaction.
- Important: Even one DML operation can push you over this if your collection is too big.
- Example:
List<Account> listToInsert = new List<Account>();
for (Integer i = 0; i < 10001; i++) {
listToInsert.add(new Account(Name = 'Acct' + i));
}
insert listToInsert;
That crashes with Too many DML rows: 10001.
If you limit it to exactly 10,000 entries, it succeeds.
5. CPU Time Limit
- Limit: 10 seconds for synchronous, 60 seconds for asynchronous execution.
- What “CPU time” means: It’s actual execution time your code spends on the Salesforce server (not wall clock time).
- When it triggers: Deep loops, nested loops, heavy logic per record, inefficient code.
If your transaction does complex calculations or touches many objects repeatedly, you might see:
System.LimitException: Apex CPU time limit exceeded
6. Heap Size / Memory Limit
- Limit: 6 MB (synchronous), 12 MB (asynchronous).
- What is “heap”: Think of it as how much memory your variables, collections, and objects consume.
- Risk: Loading huge query results into memory, or creating enormous data structures, can push you past this limit and cause:
System.LimitException: Apex heap size too large
Quick Reference: Salesforce Governor Limits Summary
| Limit Type | Description | Synchronous Limit | Asynchronous Limit | Common Error Message |
| SOQL Queries | Total number of SOQL queries that can be executed per transaction | 100 | 200 | Too many SOQL queries: 101 |
| Records Retrieved via SOQL | Total records that can be fetched in all queries | 50,000 | 50,000 | Too many query rows: 50001 |
| DML Statements | Number of insert, update, delete, or upsert statements | 150 | 150 | Too many DML statements: 151 |
| DML Rows Processed | Total records that can be processed by DML | 10,000 | 10,000 | Too many DML rows: 10001 |
| CPU Time Limit | Maximum processing time per transaction | 10 seconds | 60 seconds | Apex CPU time limit exceeded |
| Heap Size Limit | Maximum memory used by variables and collections | 6 MB | 12 MB | Apex heap size too large |
| Callouts (HTTP Requests) | Number of callouts allowed per transaction | 100 | 100 | Too many callouts: 101 |
| Future Calls / Queueable Jobs | Number of async Apex jobs that can be queued | 50 per transaction | 50 per transaction | System.LimitException: Future calls limit exceeded |
| Email Invocations | Number of email messages that can be sent | 10 | 10 | Too many Email Invocations |
Note: These are standard governor limits, but Salesforce may occasionally adjust limits depending on platform updates or org type. Always refer to the official documentation for the most current details.
How to Check Governor Limits in Dev Console?
Here’s the step-by-step method shown in the video (and in common practice) to observe how many resources your code consumes:
- In Salesforce, click the gear icon → Developer Console
- In Dev Console, go to Debug → Open Execute Anonymous Window
- In the Execute Anonymous pop-up:
- Type or paste your Apex snippet
- Tick “Open Log” (so you can view log immediately)
- Click Execute
- In Dev Console, go to the Logs tab
- Double-click the latest log
Scroll toward the bottom – you’ll see details like:
Number of SOQL queries: 101 out of 100
Number of DML statements: 1 out of 150
Number of DML rows: 1 out of 10000
- This section shows exactly which limits were consumed and which were exceeded.
More Types of Limits & Edge Cases
The video also touches (briefly) on other limits and patterns you should be aware of:
- Asynchronous Limits
In async contexts (future, batch, queueable), limits are higher (e.g. 200 SOQL, 12 MB heap, 60 sec CPU) - Batch Apex
Each batch chunk runs in its own transaction with its own governor allotment - Platform / Static / Global Limits
Limits that span across many transactions (e.g. total code size, number of jobs, API calls) - Flow / Process Builder / Automation Limits
Automations also consume the same DML, SOQL limits, so your Apex + Flow combination must co-exist carefully - Recursive / Trigger Limits
Avoid infinite recursions by adding guard logic (static flags, “already run” checks)
Best Practices to Stay Within Limits (Golden Rules + Tips)
Here’s how the video suggests (and best practices among Salesforce devs reinforce) to design clean, limit-safe code:
1. Bulkify Everything
Never query or DML inside loops. Always gather all needed IDs/data, then operate in bulk.
Bad:
for (Contact c : Trigger.new) {
Account a = [SELECT Id, Name FROM Account WHERE Id = :c.AccountId];
c.SomeField = a.Name;
}
update Trigger.new;
Better:
Set<Id> acctIds = new Set<Id>();
for (Contact c : Trigger.new) {
acctIds.add(c.AccountId);
}
Map<Id, Account> acctMap = new Map<Id, Account>(
[SELECT Id, Name FROM Account WHERE Id IN :acctIds]
);
for (Contact c : Trigger.new) {
Account a = acctMap.get(c.AccountId);
if (a != null) {
c.SomeField = a.Name;
}
}
update Trigger.new;
2. Use Collections (Lists, Maps, Sets)
Group records and operate in a single DML or query rather than many small ones.
3. Query Smart
- Fetch only the fields you need
- Use WHERE filters to narrow results
- Use LIMIT where possible
- Leverage relationship (parent-child) queries to reduce number of SOQL statements
4. One Trigger Per Object Pattern
Avoid having multiple triggers (before/after) on the same object, it’s hard to control order. Use a handler class pattern so your logic is organized and easily debuggable.
5. Avoid Deep Nesting & Heavy Logic in Loops
Nested loops, deep recursion, or heavy computations per record can burn CPU time. Wherever possible, break tasks or delegate to asynchronous execution.
6. Manage Recursion / Prevent Infinite Loops
Use static variables or “hasRun” flags so triggers or methods don’t re-enter themselves infinitely.
7. When in Doubt – Use Asynchronous Processing
If you’re handling large data sets or heavy processing:
- @future methods
- Queueable Apex (more flexible chaining)
- Batch Apex (best for millions of records)
- Scheduled Apex (for timed operations)
Each gives you a fresh transaction with higher limits and better handling of bulk data.
Governor limits are just the start of your Salesforce journey. If you’re curious to push your skills further, explore our full library of tutorials on YouTube and discover more hands-on learning at 2Creative Solutions