Bypassing the “Number of Iterations Exceeded” Error in Salesforce Flows with Platform Events
Elements Limit in Flows
Flows in Salesforce are great, but there are some limits that can make working with them very frustrating. One of those limitations is the executed elements per-flow which are limited to 2000 [1]. The elements are not the actual loop iterations in a flow but the blocks of the flow that are evaluated. If a loop has 4 blocks and runs 10 times, 10*4=40 elements are executed. This makes it very easy to exceed the 2000 elements limit, even when iterating over a few hundred records. Usually, the solution is abandoning the flow and resorting to writing some Apex code instead.
So the question is “How do we get around that without code?”. Or in a more technical sense, “How do we make the flow evaluate less than 2000 elements and evaluate the rest in another flow”.
One way is by using platform events.
A Simple Loop
Let’s build a simple flow that iterates through a number of records and updates them. As we use it for more records we’ll get the “Number of Iterations Exceeded” [2] error.
This example flow updates all the contacts of an account after a field in the account is updated. Let’s do that with the address fields assuming that we want to update the mailing address of the contacts with the account’s shipping address.
Fig. 1: Simple flow loop
I have created an auto launched flow that can be launched from the Process builder when accounts are updated. First, we have to create an input variable for the Account that is the parent of the contacts to be updated. The Account will be passed from the Process Builder to our flow. Let’s name our variable AccountIn and mark it as “Available for Input”.
Fig. 2: Setting the input variable.The next step is to create a collection variable for all the contacts that we will want to update. Let’s name this ContactsToUpdate and allow multiple values.
Fig. 3: Creating the collection variable for the contacts to be updated.Now that we’re all set up with the variables, let’s start building the flow. The first step is a Get Records element to get all the contacts of the Account by using the condition AccountId=AccountIn.AccountId.
Fig. 4: Getting the related contacts.Don't forget to check out: Learn All About Flows in Salesforce and Its Powers
The next step is to iterate through our collection using a Loop element. The Collection Variable is the one automatically created by the “Get Contacts” element.
Fig. 5: Contacts loop.Next, we’ll assign the values of the Account shipping address fields to the Contact shipping address fields using an assignment block. The assignment we are doing is:
- Contact.MailingPostalCode ← Account.ShippingPostalCode
- Contact.MailingCity ← Account.ShippingCity
- Contact.MailingCountry ← Account.ShippingCountry
- Contact.MailingStreet ← Account.ShippingStreet
After the assignment of the values, we need to add the current loop item to the ContactsToUpdate collection using another Assignment element.
Fig. 7: Adding the loop element to the ContactsToUpdate collection.Finally, we have to close the loop and after the loop is finished we use an Update Records element to update all the records in the ContactsToUpdate collection.
Fig. 8: Updating the records before finishing the flow.Our flow has 2 elements outside the loop and 3 loop elements. This is translated to 2+3*X element evaluations for X contact records to be updated. We would hit the 2000 elements limit with 666 contacts.
Creating Data for Testing
To do some testing we have to create a few accounts with different numbers of related contacts. To do that I’ll be using Forceea [3], an open-source data factory tool that is great for populating sandboxes with thousands of contacts for testing. After the accounts and the contacts are created, I run a query to make sure they are as expected.
Fig. 9: Number of contacts for each account.
As expected, running our flow for the Company-20 (20 contacts) and the Company-200 (200 contacts) accounts works great. When we run it for the Company-2k (2000 contacts) account, however, we get the “Number of Iterations Exceeded Error”.
Using a Platform Event to Trigger the Flow
Platform events are messages that can deliver custom notifications within the Salesforce platform or to external apps asynchronously. The idea behind using a platform event is to use a platform event to trigger the flow (previously it was called directly from the Process Builder) and the flow can then create another platform event to call another instance of itself before hitting the iterations limit. The new flow is not another interview of the same flow but an entirely new flow that has no access to the previous flow’s data. After the next flow finishes updating the records, it will create another platform event if there are still remaining records. Otherwise, it updates the records and stops. The process is depicted in the following diagram.
Fig. 10: Process with flow and platform events combination.Creating the Platform Event
Before jumping into the flow design, let’s create a new platform event that triggers the flow. The information that we need in the event is the Account Id. From Setup>Platform Events and then New Platform Event we create a new platform event.
Fig. 11: Creating the platform event that initiates the flow.Next, we need to add a custom field for the Account Id.
Fig. 12: Adding a custom field for the Account Id (1/2). Fig. 13: Adding a custom field for the Account Id (2/2).Refactoring the flow
We’re done creating our custom platform event. Now we have to create a new flow that will be launched by a platform event.
Fig. 14: Creating a Platform Event-Triggered Flow.Our flow should look like this:
Fig. 15: Flow diagram with platform event.Check out an amazing Salesforce video tutorial here: When to Use Apex? | Flows of Action in Apex | Learn Salesforce Development
It’s still very similar but there are a few differences. First, we need to get the account record that initiated the flow since the flow doesn’t have access to it. We also need to add some elements to keep track of the loop count and if the limit has been reached. Finally, we need to be able to call another instance of the same flow by creating another platform event.
The first step is to create the variables and constants we will need.
- LoopCounter
- LoopLimit
- LimitReached
- ContactsToUpdate
The LoopCounter variable keeps track of the current iteration. Remember, the max number of iterations depends on the number of elements in the loop.
Fig. 16: Loop counter variable.The LoopLimit constant is the maximum number of iterations before stopping the flow and invoking another one.
Fig. 17: The LoopLimit variable keeps track of the max number of loop iterations.Next, we can define a formula variable to check when it’s time to pause.
Fig. 18: The LoopLimitReached variable checks when the limit has been reached.The ContactsToUpdate variable is simply a collection of contacts like before.
We can now start building the loop. First, we need to define the platform event that triggers our flow by clicking on the start element.
Fig. 19: Selecting the appropriate platform event.Next, we need to get the Account record and its Contacts using the Account Id information stored in the platform event. Since this flow is triggered by a Platform Event, the Account record is not available and we need to get it using the Platform Event’s Account Id field.
Fig. 20: Getting the account record from the Account Id in the platform event.The next step is getting the related Contacts. Here however we need to be a bit more selective than before. Since this flow is going to update some of the contacts and then call itself again using another platform event, we’ll need to only get the Contact records that have not yet been updated. In other words, the conditions we need to use for our case are the following:
- Contact.AccountId = UpdateContactsEvent.AccountId__c
- Contact.MailingPostalCode ≠ Account.ShippingPostalCode
- Contact.MailingCity ≠ Account.ShippingCity
- Contact.MailingCountry ≠ Account.ShippingCountry
- Contact.MailingStreet ≠ Account.ShippingStreet
With the following custom condition logic: 1 AND (2 OR 3 OR 4 OR 5).
Fig. 21: Setting the contacts query logic.We’ll keep track of the loop counter with an assignment element that adds 1 to the loop counter every time it’s evaluated.
Fig. 22: Loop counter update.The next element is a decision to check if the iteration limit has been reached.
Fig. 23: Checking if the loop limit has been reached with a decision element.The default outcome is when the limit hasn’t been reached so the loop should continue. When the limit has been reached, we should update the records that we have processed and then create another platform event to trigger a new flow that can handle the rest of the records.
Fig. 24: Updating the records before creating another platform event. Fig. 25: Creating a platform event to launch another instance of this flow in order to handle the remaining records.That’s it. Now our flow can handle thousands of records without hitting the iteration limit of 2000 elements. Just keep in mind that platform events can have their own limitations and processing will run asynchronously so updating the records won’t be instant.
Vasilis Papanikolaou
Vasilis is a Salesforce consultant, developer and Partner at Robin Consulting. At Robin, we provide strategic advice, process definition and technology solutions that help companies grow.
References
[1] General Flow limits: https://help.salesforce.com/s/articleView?id=sf.flow_considerations_limit.htm&type=5
[2] Flow Error "Number of Iterations Exceeded" https://help.salesforce.com/s/articleView?id=000314438&type=1
[3] Forceea Data Factory
Hi!
What if I use a pause element if the limit is exceeded?
Would that work?
Hi Leo,
It's something I had tried but I was getting another error when the number of records was large. If I remember correctly, it had to do with the max amount of memory used by the flow. Feel free to share if you try it.
How do you trigger the Platform Event Flow to run?
Hi Jason,
You have to select the triggering event in the start element of the flow. Make sure your flow is a Platform Event-Triggered flow and not a Record-Triggered flow. Check out figure 19. The same flow can trigger another instance of itself by creating another platform event.