Apex CPU time limit exceeded

Let me walk you though how can we handle Apex CPU time limit exceeded.

Apex CPU time limit exceeded

When we initiate a request, every request will be allocated 10 secs of CPU time.

However, that’s not the case with Asynchronous Apex.

With Async Apex, it's 60 secs, which is 6X times as that of CPU time allocated to Synchronous Apex. You can find more about the governor limits of Asynchronous Apex here.

When the business logic that we have in place doesn’t get executed in 10 secs time frame that's when we get to encounter Apex CPU time limit exceeded exception.

Also, when a request is initiated few things are calculated against the 10 secs CPU time and a few things are not counted against them.

What is counted?

  • All Apex code
  • Library functions exposed in Apex
  • Workflow execution

What is not counted?

  • Database operations, e.g. DML, SOQL
  • SOSL
  • HTTP callouts

1. Aggregate SOQL usage

All the information that can be calculated using SOQL queries, it's better that we push it to SOQL by make proper use of notions like AVG(), SUM(), Count() and etc, rather than writing the business logic to calculate.

Because, when we write the logic to aggregate the results it will be counted against the 10 secs CPU time.

2. Use map based query

Using map based queries reduce the processing time considerably.

Likewise, do not write the logic to create a map with key as id of the record and value as the record itself, use inline maps.

//rather do this
Map<id,account> accountsMap = new Map<id,account>([Select Id,Name from Account limit 50000]);

//Creating list of accounts
List<account> accList = accountsMap.values() ;

//Creating set of ids

Set<id> accIds = accountsMap.keySet() ;

3. Avoid nested for loops

When we have large data sets, quite often I have seen developers using nested for loops to process them.

It's better we avoid using nested for loops, it throttles the server resources.

//don't do this 

List<Account> accounts = [SELECT Id, Name FROM Account];

for(Account a : accounts){

	List<Contact> contacts = [SELECT Id, FirstName, LastName FROM Contacts WHERE AccountId =: a.Id];
	
	for(Contact c: contacts){
		System.debug(' 🚀 ' +c.Id);
	}
}

Rather do this ..

We can do the same thing very easily using inner queries, but I wanted to demonstrated how to put queries out of loops along with collections to build a solution that follows best Practices in Apex.

Having said that, at times in the process of reducing nested for loops we tend to complicate the business logic and in such scenarios it's better we leave the nested for loops as it is.

4. Check if the business logic can be run asynchronously

Last, check if you can push some part of the business logic to Async Apex.

That way the business logic in Async Apex will be executed in a different context/thread when compared to the main thread.

This considerably effects the performance.

In case you are looking for some complex scenarios in batch apex, this might be helpful
- Batch Apex, Inner Queries and Iterators
- REST API in Batch Apex, errors and maintain state

Other frequently encountered errors

Interested in learning LWC Development with me? Subscribe to my course (use this if you are out of India).