CPU Time Limit Exceeded in Apex: Causes & Solutions
You’re running a Salesforce Apex transaction, everything looks fine in the sandbox, and then — boom. System.LimitException: Apex CPU time limit exceeded. It’s one of those errors that feels mysterious until you understand what’s actually happening under the hood.
The good news? It’s almost always fixable. Let’s walk through what causes this error and, more importantly, how to solve it.
Table of Contents
- Understanding CPU Time Limits
- Common Causes
- Optimization Strategies
- Moving to Asynchronous Processing
- Profiling CPU Usage
- Code Examples
- Platform Events Alternative
- FAQ
Understanding CPU Time Limits
Salesforce enforces governor limits to ensure that no single Apex transaction monopolizes shared resources on its multi-tenant platform. CPU time is one of the most critical of these limits.
In plain terms: CPU time measures the actual computation time your Apex code consumes — not wall-clock time, but the time the processor spends executing your logic.
Synchronous vs. Asynchronous Limits
Salesforce sets different CPU time limits depending on how your code runs:
| Context | CPU Time Limit |
|---|---|
| Synchronous Apex | 10,000 milliseconds (10 seconds) |
| Asynchronous Apex (Batch, Queueable, Future) | 60,000 milliseconds (60 seconds) |
That 6x difference is significant. If you’re running into the synchronous limit regularly, moving to async processing is often the fastest solution.

Read this Official Salesforce documentation Governor limits in Salesforce.
What Counts Toward CPU Time
Not everything counts. Salesforce only tracks time your code is actively computing. Here’s what’s included:
- Apex code execution (loops, conditionals, assignments)
- SOQL result processing and data manipulation in memory
- String manipulation and mathematical operations
- Trigger logic and class instantiation
What’s excluded:
- Time waiting for SOQL queries to return results
- DML statement wait time
- Callout response time
- Email send operations
This matters because you can have a slow-running transaction that looks like a CPU issue but is actually a SOQL performance problem — or vice versa.

Read this documentation on how to avoid SOQL 101 Error.
Common Causes

Most cpu time limit exceeded apex errors trace back to a handful of patterns. Once you know what to look for, they’re not hard to spot.
Complex Loops
Loops are the #1 cause of CPU time issues. A loop iterating over 50,000 records with logic inside it can burn through your 10-second limit fast.
The problem gets worse when you nest loops. An outer loop over 1,000 records, each triggering an inner loop over 100 items, gives you 100,000 iterations. That’s a recipe for a CPU error.
Heavy Computational Logic
Some operations are just CPU-heavy by nature:
- String parsing and regex matching
- Complex JSON serialization/deserialization
- Recursive method calls
- Large in-memory object graph traversal
If you’re parsing a large XML payload inside a trigger, for example, you’ll feel it in your CPU budget quickly.
Inefficient Algorithms
An O(n²) algorithm that works fine with 100 records starts failing with 5,000. The most common offender is SOQL inside loops — but even pure in-memory logic can be O(n²) if you’re doing linear searches through a list when a Map would be O(1).
The golden rule: never write code that assumes small data volumes. Triggers run on bulk operations in production.
Optimization Strategies
Here’s where you actually fix the problem. Most optimizations fall into three categories.
Algorithm Optimization
The biggest wins come from improving your algorithm’s time complexity, not micro-optimizations.
Replace linear searches with Maps:
Before:
Apex Code
for (Account acc : accounts) {
for (Contact con : contacts) {
if (con.AccountId == acc.Id) {
// do something
}
}
}
After:
Apex Code
Map<Id, List<Contact>> contactsByAccount = new Map<Id, List<Contact>>();
for (Contact con : contacts) {
if (!contactsByAccount.containsKey(con.AccountId)) {
contactsByAccount.put(con.AccountId, new List<Contact>());
}
contactsByAccount.get(con.AccountId).add(con);
}
for (Account acc : accounts) {
List<Contact> relatedContacts = contactsByAccount.get(acc.Id);
// do something
}
This takes you from O(n × m) to O(n + m). For large data sets, this is the difference between success and failure.
Reducing Nested Loops
If you spot nested loops in your code, treat it as a red flag. Ask yourself: can the inner loop be replaced with a Map lookup?
You should also look at whether the inner loop runs unnecessarily. Can you break early with a break or return statement? Can you pre-filter data before the loop starts?
Using Efficient Data Structures
Salesforce Apex gives you Maps, Sets, and Lists. Use each for what it’s good at:
- Map: Fast key-based lookup (O(1)) — use when you need to find records by Id or other keys
- Set: Fast membership checks — use when you need to know “is this Id in my collection?”
- List: Sequential access — use when order matters or you need index-based access
Switching a List-based lookup to a Set or Map is often the single biggest performance improvement you can make.

Read this documentation around Apex Data Types and Collections
Moving to Asynchronous Processing
Sometimes the logic is legitimately complex and can’t be simplified further. That’s when async processing becomes the right architectural choice.
When to Go Async
Consider moving to async when:
- Your trigger logic is complex but can tolerate a slight delay
- You’re processing large volumes that will always bump against sync limits
- The work doesn’t need to complete in the same transaction as the triggering event
- You need the 60-second CPU budget instead of 10 seconds
Queueable and Batch Options
Queueable Apex is ideal for jobs that need to chain and pass complex state between steps. It’s more flexible than @future methods and supports object parameters.
Apex Code
public class HeavyProcessingJob implements Queueable {
private List<Id> recordIds;
public HeavyProcessingJob(List<Id> ids) {
this.recordIds = ids;
}
public void execute(QueueableContext context) {
// Your heavy logic here — 60 seconds of CPU budget
List<Account> accounts = [SELECT Id, Name FROM Account WHERE Id IN :recordIds];
// process...
}
}
// Enqueue it from your trigger
System.enqueueJob(new HeavyProcessingJob(triggerAccountIds));
Batch Apex is the right choice for very large data sets — think millions of records. It processes records in configurable chunks (default 200, max 2,000 per execute) and each chunk gets its own governor limit allocation.
Apex Code
public class AccountBatchProcessor implements Database.Batchable<sObject> {
public Database.QueryLocator start(Database.BatchableContext bc) {
return Database.getQueryLocator('SELECT Id, Name FROM Account');
}
public void execute(Database.BatchableContext bc, List<Account> scope) {
// This runs for each chunk — fresh CPU time for each batch
}
public void finish(Database.BatchableContext bc) {}
}

This documentation explains in in depth asynchronous Apex in Salesforce.
Profiling CPU Usage
Before you optimize, you need to know where the CPU time is actually going. Guessing wastes time.
Debug Logs Analysis
Enable debug logs in Salesforce Setup and set the Apex Code log level to FINEST. Run your transaction and then analyze the log.
Look for entries with CUMULATIVE_LIMIT_USAGE at the end of the log — this shows you exactly how much CPU time was consumed. You can also search for ENTERING_MANAGED_PKG entries if you suspect a managed package is eating your CPU budget.
The key is finding the method calls that consume the most time. Debug logs include timing information for each method entry/exit, so you can identify hotspots.
Limits Class Monitoring
Use System.Limits to add instrumentation directly to your code during development:
Apex Code
Integer cpuBefore = Limits.getCpuTime();
// Your code block here
Integer cpuAfter = Limits.getCpuTime();
System.debug('CPU used by this block: ' + (cpuAfter - cpuBefore) + 'ms');
System.debug('Total CPU limit: ' + Limits.getLimitCpuTime() + 'ms');
System.debug('Remaining CPU: ' + (Limits.getLimitCpuTime() - cpuAfter) + 'ms');
This tells you precisely which sections of your code are expensive. It’s the most reliable way to isolate the problem before committing to a refactor.
Code Examples
Here’s a complete before/after example showing a common real-world pattern that triggers CPU limit errors — and how to fix it.
Scenario: A trigger on Opportunity updates related Contact records based on Opportunity data.
Before (problematic):
Apex Code
trigger OpportunityTrigger on Opportunity (after update) {
for (Opportunity opp : Trigger.new) {
// SOQL inside loop — also a CPU issue from processing
List<Contact> contacts = [
SELECT Id, Description
FROM Contact
WHERE AccountId = :opp.AccountId
];
for (Contact con : contacts) {
con.Description = 'Last Opp: ' + opp.Name;
}
update contacts;
}
}
After (optimized):
Apex Code
trigger OpportunityTrigger on Opportunity (after update) {
// Collect all Account Ids first
Set<Id> accountIds = new Set<Id>();
Map<Id, Opportunity> oppByAccount = new Map<Id, Opportunity>();
for (Opportunity opp : Trigger.new) {
accountIds.add(opp.AccountId);
oppByAccount.put(opp.AccountId, opp);
}
// Single SOQL query outside the loop
List<Contact> contacts = [
SELECT Id, AccountId, Description
FROM Contact
WHERE AccountId IN :accountIds
];
// Single pass through contacts using Map lookup
for (Contact con : contacts) {
Opportunity relatedOpp = oppByAccount.get(con.AccountId);
if (relatedOpp != null) {
con.Description = 'Last Opp: ' + relatedOpp.Name;
}
}
update contacts;
}
The optimized version replaces nested loops and multiple DML calls with a single query, a Map, and a single update — massively reducing both CPU time and DML consumption.
Platform Events Alternative
For scenarios where you need to decouple processing entirely, Platform Events offer an elegant alternative to synchronous triggers and even traditional async jobs.
Instead of processing heavy logic in a trigger, your trigger publishes a Platform Event. A separate subscriber processes the event asynchronously — completely decoupled from the original transaction.
Apex Code
// In your trigger — lightweight, fast
EventBus.publish(new Account_Updated__e(
AccountId__c = acc.Id,
ChangedField__c = 'Industry'
));
// In a separate Platform Event trigger — runs async
trigger AccountUpdatedHandler on Account_Updated__e (after insert) {
for (Account_Updated__e event : Trigger.new) {
// Heavy processing logic here
// Has its own CPU budget
}
}
This pattern is especially useful when you need near-real-time processing but can’t afford to put heavy logic in the critical path of a user-facing transaction. It also makes your system more resilient — if the subscriber fails, it can be retried independently.
Decision Tree: What Should I Do?
Use this to quickly diagnose your CPU limit error and pick the right fix:
Step 1 — Is the error in a trigger?
- Yes → Check for nested loops and SOQL inside loops. Refactor using Maps and bulkified queries.
- No → Use
Limits.getCpuTime()to identify which method or class is consuming the most time.
Step 2 — Still hitting limits after optimizing?
- Logic is complex but a short delay is acceptable → Move to Queueable Apex
- Processing thousands or millions of records → Use Batch Apex
- Need full decoupling from the original transaction → Use Platform Events
Step 3 — Is the issue inside a managed package?
- You can’t modify managed package code directly. Contact the vendor or restructure your logic to reduce the number of records processed per transaction.
FAQ
What is the Apex CPU time limit in Salesforce?
Synchronous Apex transactions have a 10,000 ms (10 second) CPU time limit. Asynchronous contexts — Batch, Queueable, and Future methods — have a 60,000 ms limit. CPU time measures active computation only, not wait time for SOQL or DML operations.
How do I fix CPU time limit exceeded in Apex?
Start by profiling with Limits.getCpuTime() to find the hotspot. Common fixes include replacing nested loops with Map-based lookups, moving SOQL outside loops, using Sets for membership checks, and moving heavy logic to Batch or Queueable Apex.
Does SOQL query time count toward Apex CPU limits?
No. Salesforce excludes the time Apex spends waiting for SOQL results, DML operations, callouts, and email sends. Only active computation in your Apex code counts toward the CPU limit.
How can I reduce CPU time in Salesforce Apex?
The most impactful ways to reduce CPU time are: replace O(n²) loops with O(n) Map lookups, avoid unnecessary string operations inside loops, pre-filter collections before iterating, and use early exits with break or return when possible.
When should I use Batch Apex vs. Queueable Apex for CPU issues?
Use Batch Apex when processing large numbers of records (thousands or millions) that can be broken into chunks. Use Queueable Apex when you need to chain jobs, pass complex state between steps, or when the volume is smaller but the per-record logic is genuinely complex.
Can managed packages cause CPU time limit exceeded errors?
Yes. Managed package code counts toward your transaction’s CPU limit — not a separate budget. If a managed package trigger is consuming most of your CPU time, contact the package vendor or reduce the number of records your own code processes in the same transaction.
How do I check CPU usage in Apex without hitting the limit again?
Use Limits.getCpuTime() before and after specific code blocks during development to measure consumption. This adds minimal overhead and gives you precise measurements without needing to reproduce the error at full data volume.
Conclusion
The apex cpu time limit exceeded error is almost always a symptom of one of three things: nested loops, inefficient data structure choices, or logic designed for small data volumes that got deployed at scale.
The fix follows a clear path: profile first, optimize algorithms before moving to async, and reach for Batch or Queueable Apex when the logic genuinely can’t fit in 10 seconds. Platform Events are worth considering for any architecture that needs true decoupling — they make CPU issues structurally impossible for that processing path.
If you’re dealing with recurring CPU limit issues across your org, it’s worth doing a broader Apex performance review. The patterns that cause CPU errors usually signal other scalability problems waiting to surface.
Have questions about Apex performance optimization or want help reviewing a specific piece of code? Drop a comment below — always happy to dig into a tricky governor limit problem.