Tuesday, August 10, 2021

Enhanced Loop(For Each) Vs Traditional Loop(For)

If you have written a trigger, you must be familiar with the enhanced for-loop.  We all must have used the enhanced for-loops in our apex code at-least once.  But are they really efficient against the traditional For-loops ? In this blog, let us dig deep into the efficiency measures of the for-loops.

Let us consider an example of iterating through list of Opportunity records and manipulating a field-value for our experiment.  Look at the below snippet where we are trying to iterate a collection in traditional and enhanced way.


From the above snippet it is so clear that enhanced for-loop is so efficient over traditional for-loop because of the reasons :

  1. Un-necessary variables are not required to be created in enhanced for-loop
  2. We don't need to calculate the size of the collection for iteration
  3. Index is not required to access an element inside collection
These reasons are enough to conclude that enhanced for-loop has won the war against traditional for-loop.   But let me tell you that traditional for-loop is efficient than enhanced for-loop in few scenarios and one such is in CPU Time Consumption.  Yes, traditional for-loop consumes lesser time than enhanced for-loop and CPU Time out exception is a nightmare for most of the developers.    Can't believe me, let me use the above example to prove my point.  Before I proceed, let me make you aware of the below system method : 

Limits.getCPUTime() Returns the CPU time (in milliseconds) that has been used in the current transaction

I'll be using the above system method to calculate the time that a block of code consumes. 

I will be running both the loops running against a collection size of 5000.  Let me first start with enhanced for-loop to see the time consumption.  
List<Opportunity> lstOpp = [SELECT Id, Name, StageName FROM Opportunity LIMIT 5000];

Integer strtTime = Limits.getCPUTime(); //Fetch the time consumed so far
//Iterate through all the opportunities
//In an enhanced for-loop by iterating through collection oppList
for(Opportunity opp : oppList){
   opp.stageName = strStgName;
}
Integer endTime = Limits.getCPUTime(); //Get the time consumed so far
//Print the time consumed by the for-loop
system.debug('Time consumed by for-loop is '+(endTime-strtTime) +'ms');

Above code resulted with the below output 
16:49:58:065 USER_DEBUG [8]|DEBUG|Time consumed by for-loop is 178 ms

Now, let us run the same code by using traditional for-loop :
List<Opportunity> lstOpp = [SELECT Id, Name, StageName FROM Opportunity LIMIT 5000];

Integer strtTime = Limits.getCPUTime(); //Fetch the time consumed so far
//Iterate through all the opportunities
//In an enhanced for-loop by iterating through collection oppList
for(Integer i=0; i<lstOpp.size(); i++){
   lstOpp[i].stageName = strStgName;
}
Integer endTime = Limits.getCPUTime(); //Get the time consumed so far
//Print the time consumed by the for-loop
system.debug('Time consumed by for-loop is '+(endTime-strtTime) +'ms');
Above code resulted with the below output 
16:52:30:323 USER_DEBUG [11]|DEBUG|Time consumed by for-loop is 107ms

It is now clear that traditional for-loop took less time than the enhanced for-loop.  Now let us make few tweaks to the traditional for-loop.  Rather than having lstOpp.size(); at the condition level, let's create a new variable for holding the same, so that calculation of collection size can be avoided at each iteration.  As .size() is present at the condition place and condition check happens for each iteration .size() calculation happens for each iteration.
List<Opportunity> lstOpp = [SELECT Id, Name, StageName FROM Opportunity LIMIT 5000];

Integer strtTime = Limits.getCPUTime(); //Fetch the time consumed so far
//Iterate through all the opportunities
//In an enhanced for-loop by iterating through collection oppList
//Created a new variable "j" to hold the size(size will be calculated only once)
for(Integer i=0, j=lstOpp.size(); i<j; i++){
   lstOpp[i].stageName = strStgName;
}
Integer endTime = Limits.getCPUTime(); //Get the time consumed so far
//Print the time consumed by the for-loop
system.debug('Time consumed by for-loop is '+(endTime-strtTime) +'ms');

Change made : Created a new variable "j" to hold the size and the size will be calculated only once unlike the prior implementation, where the size calculation happens for each iteration.

16:52:30:323 USER_DEBUG [11]|DEBUG|Time consumed by for-loop is 97ms

PS : You may not get the same exact output when you execute the above snippet in your ORG, values will always be approximately derived.

From the above execution, we understood that traditional for-loop took just half the amount of time Enhanced for-loop consumes.  This reason is enough to conclude that traditional for-loop is efficient over Enhanced For-Loop.  If you have to iterate child records which comes as part of inner SOQL Query, traditional for-loop will take just 1/3rd time of enhanced for-loop.

Then why do we have Enhanced For-Loop ?
Everyone would have started their coding exercise only through traditional for-loop. If traditional for-loop is that great, then why do we have enhanced for-loop.  ? Do we have to replace all our enhanced for-loops with traditional for.  ? 

Answer is BIG "NO".  There are instances where enhanced for-loop is much efficient than the traditional one and one good example is HEAP size consumption.  Enhanced for-loop consumes very less heap size when compared with traditional for-loop.  I recommend you to use methods of "Limits" class to check the heap size, time consumption on your loops and then take a call on the kind of for-loop that better suits your purpose.  Hope you find this interesting. 

Thanks for being an awesome reader.   Feedback is much appreciated.

..Bazingaa..

7 comments: