4

Prelude

According to Salesforce documentation, both Sets and Lists are considered Collections: https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/langCon_apex_collections.htm


Examples

This code...

List<String> list_string = new List<String>{'one', 'two', 'three'};
for (String str : list_string) {
    String str2 = String.valueOf(Math.random());
    list_string.add(str2);
}
System.debug(list_string);

...fails as expected with this error:

Cannot modify a collection while it is being iterated.


However, this code...

Set<String> set_string = new Set<String>{'one', 'two', 'three'};
for (String str : set_string) {
    String str2 = String.valueOf(Math.random());
    set_string.add(str2);
}
System.debug(set_string);

...runs without error, resulting in something like:

{0.43617097809531613, 0.8996138104361924, 0.9077738610155754, one, three, two}


Question

Why?

Secondary Question: Even if it does not error, why doesn't the above Set iteration not run infinitely? Maybe the answer explains both....


More Info

The only mentions I can find in the Apex Developer Guide (ADG) pages on Lists and Sets regarding iteration is for Sets:

The iteration order of set elements is deterministic, so you can rely on the order being the same in each subsequent execution of the same code.

That sounds a lot like a Set is treated as a List for iteration, so it makes even less sense to me why a Set can be modified but a List cannot.

Nothing is mentioned in the ADG List or Set Iteration for Loops section.

1 Answer 1

4

In Iterating Collections, we are instructed:

Collections can consist of lists, sets, or maps. Modifying a collection's elements while iterating through that collection is not supported and causes an error. Do not directly add or remove elements while iterating through the collection that includes them.

This behavior is therefore a bug. The deterministic ordering of elements is likely the cause. I'm not an internal R&D person, but I strongly suspect the Set is copied when an iterator is created for the set, so that all of the original elements will be iterated, without infinite recursion.

The same bug occurs with removal:

Set<String> set_string = new Set<String>{'one', 'two', 'three'};
for (String str : set_string) {
    set_string.remove(str);
}
System.debug(set_string); // {}

At least it's consistent. Do not rely on this behavior. I will be reporting this as a bug, and hopefully it will be fixed.

4
  • Yes, please raise this as a bug, and let me know the case # here. Mar 15 at 19:42
  • 1
    @DanielBallinger #46568560
    – sfdcfox
    Mar 15 at 20:00
  • 2
    Thanks. That will get linked up to our internal bug that I've filed for it. Calling .clear() on the Set within the for loop makes for a particularly dramatic example of the bug here. It's likely we will need to API version this change. While it isn't desired behavior, it's possible that someone could be inadvertently depending on it. Mar 15 at 20:29
  • @DanielBallinger Thanks. I saw it doing a code review. It was not really consciously/intentionally coded this way by the dev, but in trying to get something to work it was sort of mindlessly thrown in. But since not only did it not error, but worked, it was left in. So there is no telling how many instances of this are in our org alone -- much less across "all the orgs."
    – Moonpie
    Mar 18 at 12:10

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .