Loop in Salesforce Apex is a block of code that is repeated until a specified condition is met. Loops allow us to do a task over and over again. We mostly use loops to iterate and search records in the list. Loops are the primary source of CPU time consumption. If we have used nested loops then it will consume resources quickly and might throw different kinds of errors in application execution. This post will help us in optimizing loop in apex Code.
Let us see some loop examples and their performance issues. We will also check how to resolve those issues.
Avoid Foreach Loop
Avoid loop iteration like the below code. Yes, it is easy to write code but it has performance issues. What is that issue?
List<Account> accs = [SELECT Id, Name FROM Account LIMIT 10000];
for(Account acc : accs){
acc.Name += 'Test1';
}
In this foreach loop, an internal iterator is created like the below code to make a loop. You can see every time an object is created and returned to the caller before the next value retrieval.
Iterator<Object> iter = source.iterator();
while(iter.hasNext()) {
Object next = iter.next();
forLoopBody.yield(next);
}
Avoid for loop with size() check
We know now what is an issue in the above foreach loop. We can create simple for loop to iterate items in a loop.
List<Account> accs = [SELECT Id, Name FROM Account LIMIT 10000];
for(Integer i = 0; i < accs.size(); ++i){
accs[i].Name += 'Test1';
}
This code is better than the above code in most scenarios but still, this is not the correct code to write in apex. What is issue in this code?
This code is using size() method which will be used for calculating the number of records in the list, so every loop iteration will calculate size which will impact our application performance. If the number of records is huge then it will take a longer time and will take time to complete for loop iterations.
So what is the correct way to write for loop to processes list?
To make a better loop to handle the record list will be calculating and store the size in one of the variables and use that variable in for loop.
List<Account> accs = [SELECT Id, Name FROM Account LIMIT 10000];
Integer size= accs.size();
for(Integer i = 0; i < size; ++i){
accs[i].Name += 'Test1';
}
This loop is better in all scenarios for performance consideration. The above two loops will also work perfectly if we are not using any DB operation like we just want to create runtime collection and do the operation on that calculation.
Access Child Loop Collection
Some time we need to access child records in loop but we might get QueryException
in SOQL for loop with the message Aggregate query has too many rows for direct assignment, use FOR loop. This exception is sometimes thrown when accessing a large set of child records (200 or more) of a retrieved sObject inside the loop, or when getting the size of such a child collection.
For example, the query in the following SOQL for loop retrieves child contacts for a particular account. If this account contains more than 200 child contacts, the statements in the for loop cause an exception.
for (Account acct : [SELECT Id, Name, (SELECT Id, Name FROM Contacts)
FROM Account WHERE Id IN ('<ID value>')]) {
List<Contact> contacts = acct.Contacts; // Causes an error
Integer countactCount= acct.Contacts.size(); // Causes an error
}
To avoid getting this exception, use a for loop to iterate over the child records, as follows.
for (Account acct : [SELECT Id, Name, (SELECT Id, Name FROM Contacts)
FROM Account WHERE Id IN ('<ID value>')]) {
Integer contactCount=0;
List<Contact> contacts;
for (Contact c : acct.Contacts) {
contacts.add(c);
contactCount++;
}
}
Optimizing Loop which can throw Heap Size Issue
Some time we fetch records and it might return so many records which might throw heap size exceed exception like querying files records in loops or fetching all records of object which might have mroe then 20,000 records.
List<Account> accounts=[SELECT Id, Name,IsVerified__c FROM Account];
for (Account acct : accounts) {
//Some Operation
acct.IsVerified__c=true;
}
update accounts;
In the above example, we are fetching records from the account object and updating the field of those records in the loop. This code will work perfectly when we have a few records in the object. When records are huge in number like more than 20,000 and might be lesser(based on what we are writing in a loop) also we can get Apex heap size too large error. If we query and process Attachment or ContentVersion object and use content in loop code then probably heap size can exceed in few records only. So how to handle that kind of loop in code?
In this kind of situation, we should update records in a batch of 200 in for loop like the below code. Using this approach we will use DML for the batch of 200 records and heap size will not increase.
for(List<Account> accountList:[SELECT Id, Name,IsVerified__c FROM Account])
{
for (Account acct : accountList) {
//Some Operation
acct.IsVerified__c=true;
}
update accounts;
}
How to Measure Heap Size:
We can use Limits class for knowing heap size in apex code execution.
- Limits.getHeapSize() – Returns the approximate amount of memory (in bytes) that has been used for the heap in the current context.
- Limits.getLimitHeapSize() – Returns the total amount of memory (in bytes) that can be used for the heap in the current context.
We can use the below method in code to know heap size is exceeding and based on that we can do alternate code to handle list or DML operation.
// check the heap size at runtime
if (Limits.getHeapSize > 275000) {
// implement logic to reduce or handle DML
}
Summary:
Loop can throw a lot of errors while SOQL or DML operation like Heap Size or too many SOQL errors if code is not written correctly. Handle loop efficiently in code so that application will not degrade performance. We can use the Limit class to know the current Heap Size and based on that we can remove some object references or we can handle some alternate approaches.
References:
Optimizing Salesforce Apex Code
Performance comparison – repeat loop vs more in one loop
8 comments
[…] Limiting data rows for lists […]
[…] Optimizing Loop in Apex Code […]
[…] Optimizing Loop in Apex Code […]
[…] Optimizing Loop in Apex Code […]
General suggestion or best practice is to avoid for loop within a for loop but I see solutions mentioned here as using for within another for loop. Is this correct? Please suggest.
Hello Sailaja,
This is only for child records of parent SOQL. We are not using a new SOQL here.
Thank You,
Dhanik
[…] this SOQL can hit the Governer Limit better to use Batch Apex to do such operations. Refer post Optimizing Loop in Apex Code to learn more about how to efficiently use […]
[…] Optimizing Loop in Apex Code […]