mdn/ai-feedback

[AI Help] Question: How does a for of loop over a set behave if values in the set are changed while looping?

danieltroger opened this issue ยท 3 comments

What is the issue with the answer?

If you execute the code the AI gave

const mySet = new Set([1, 2, 3]);

for (const value of mySet) {
  console.log(value);
  mySet.add(value + 1);
}

It will never finish executing and print an infinite amount of values, contrary to what the AI said ("But these new values will not be included in the ongoing iteration of the loop")

How would you categorize this issue?

  • The answer is harmful / unsafe.
  • The answer is not correct.
  • The answer is not helpful.
  • The answer is outdated.

What did you ask?

  1. How does a for of loop over a set behave if values in the set are changed while looping?

What did AI Help answer?

When a for...of loop iterates over a Set, it operates on the values of the Set one by one in sequential order. Each iteration of the loop executes statements that may refer to the current value.

If the values in the Set are changed while looping, the behavior can be unpredictable. The loop will continue to iterate over the original values in the Set at the time the loop started. Any changes made to the Set during the loop will not be reflected in the ongoing iteration.

Here is an example to illustrate this behavior:

const mySet = new Set([1, 2, 3]);

for (const value of mySet) {
  console.log(value);
  mySet.add(value + 1);
}

In this example, the loop will iterate over the initial values of the Set (1, 2, and 3). However, as the loop progresses, the values 2, 3, and 4 will be added to the Set. But these new values will not be included in the ongoing iteration of the loop. The loop will only iterate over the original values 1, 2, and 3.

Therefore, if you need to modify the values in a Set while looping, it is recommended to create a new Set or use another approach to ensure that the loop operates on the updated values.

What MDN pages did AI Help consult?

@danieltroger Thank you for reporting this issue. ๐Ÿ‘

As we're working on an iteration, I would like to share with you what AI Help 2.0 might answer.

Can you please check how the following answer performs with regard to the issue you reported above? ๐Ÿ™

When using a for...of loop to iterate over a Set, the loop will reflect the state of the Set at the moment the loop begins executing. If you modify the Set during iteration (such as adding or deleting items), the for...of loop may or may not visit the modified values, depending on the nature of the modification:

  1. Adding new items: If you add new items to the Set during iteration, the for...of loop might visit the newly added items. This is because Set objects are iterated in the order of insertion, and the iterator produced for the for...of loop will continue to visit any additional items that are appended during iteration.

  2. Deleting existing items: If you delete items from the Set that have not yet been visited by the for...of loop, those items will not be visited. If you delete an item that has already been visited, it has no effect on the iteration because the loop has already moved past that item.

Here is an example to illustrate this behavior:

const mySet = new Set([1, 2, 3, 4]);

for (const value of mySet) {
  console.log(value); // Logs the current value

  if (value === 2) {
    mySet.delete(3); // Deletes an item that has not been visited yet
    mySet.add(5);    // Adds a new item
  }
}

// Output:
// 1
// 2
// 4
// 5

In the example above, the 3 is deleted before it is visited, so it is not logged. The 5 is added during the iteration, and since it is appended to the Set, it is visited by the loop.

It's important to note that modifying a collection while iterating over it can lead to unpredictable behavior and is generally discouraged. If you need to modify the Set while iterating, it may be safer to collect the changes you want to make during iteration and then apply them after the loop has completed.

For more information on the for...of loop and its behavior with iterables, you can refer to the MDN documentation on for...of.

Hi @caugner, that answer is much better and seems correct. Only thing I wonder is why changing while iterating is discouraged when it's such a logical and well-defined behavior.

Thanks for the review, much appreciated.

Here's what AI Help 2.0 might answer if you asked your comment as a follow-up question:

2. why is changing while iterating discouraged when it's such a logical and well-defined behavior?

Iterating over a collection while modifying it can lead to unpredictable behavior and bugs that are hard to trace. This is not specific to JavaScript but is a common concern in many programming languages. The main reasons for discouraging modifications during iteration are:

  1. Concurrent Modification: If you modify a collection while iterating over it, you might be adding or removing elements, which can affect the iteration process. For example, in a loop, you might skip elements or process an element more than once because the underlying collection has changed.

  2. Iterator Invalidation: Some collections use iterators that keep track of the current position in the collection. Modifying the collection might invalidate the iterator, leading to errors or undefined behavior. For example, adding elements to an array while iterating over it might change the length of the array and cause the loop to access an undefined index.

  3. Logical Complexity: Modifying a collection while iterating over it can make the code harder to understand and reason about. It introduces additional complexity because you have to keep track of the changes to the collection and how they affect the iteration.

  4. Performance Implications: Depending on the collection and the nature of the changes, modifying it while iterating can have performance implications. For example, removing elements from an array while iterating might require shifting all subsequent elements, which is an O(n) operation.

In JavaScript, the behavior of modifying a Set or Map during iteration is well-defined. The Set and Map objects in JavaScript are designed to handle concurrent modifications. The for...of loop will iterate over the elements of a Set in the order they were inserted, and if you add new elements to the Set during iteration, those elements might be iterated over if they are added before the iterator reaches the end of the collection. If you delete elements, they will not be visited if they haven't been visited yet.

Here's an example based on the MDN documentation for for...of:

let mySet = new Set([1, 2, 3]);

for (let value of mySet) {
  console.log(value); // logs 1, 2, 3

  if (value === 2) {
    mySet.delete(1); // 1 is removed from the set
    mySet.add(4);    // 4 is added to the set
  }
}
// Depending on the timing of the add/delete, the loop might or might not log 4.

Even though JavaScript's Set and Map objects can handle concurrent modifications, it's still generally best practice to avoid modifying a collection while iterating over it to prevent logical errors and maintain code clarity. If modifications are necessary, consider collecting changes to apply after iteration, or use a different iteration strategy that accounts for the modifications.