78

Safe code for new Set() may look like:

let items = []; for (let item of set) if (isBad(item)) items.push(item); for (let item of items) set.delete(item) 

Can I simplify code to:

for (let item of set) if (isBad(item)) set.delete(item); 

Safe code for new Map() may look like:

let keys = []; for (let [key, val] of map) if (isBadKey(key) || isBadValue(val)) keys.push(key); for (let key of keys) map.delete(key) 

Can I simplify code to:

for (let [key, val] of map) if (isBadKey(key) || isBadValue(val)) map.delete(key) 
0

2 Answers 2

103

Yes, you can simplify to that, it's totally safe.

  • Sets and Maps are always iterated in insertion order
  • Deleting an item does not affect the position of any iterator - you can visualise the shape of the collection not being changed, just being emptied.
  • So: elements that are deleted and have not yet been iterated won't be iterated
  • Elements that have already been iterated and are deleted (like in your case) won't affect anything but other iterations/lookups.
  • Elements that are added (and are not already part of the collection) during the iteration will always be iterated

From that last point follows that the only dangerous thing to do would be something like

const s = new Set([1]); for (let x of s) { s.delete(x); s.add(1); } 

but not because of undefined behaviour or memory accumulation, but because of the infinite loop.

Sign up to request clarification or add additional context in comments.

5 Comments

Can you please add a link to reference documentation that corroborates your statement?
@jtbandes I don't know any documentation that states exactly this (you might want to try MDN however), the conclusions in my answer directly follow the observable semantics of collections in the spec as lists of entries.
There's a note under Map#forEach spec saying similar things.
You should say "Yes it's safe" instead of just "Yes" because the title starts by "Is it dangerous...".
It's 2024, and this is currently the best answer on all of SO.
8

I would say yes, it's safe. When you iterate over the Set/Map using for ... of under the hood the loop is going through @@iterator. And Iterator operates with .next() only: so no indices and no matter what is before the current position. Only one next element is important.

So until you remove elements "in front of" the current iterator position - it's safe to do it.

9 Comments

What if Set() implemented as binary tree? Removing node may cause tree re-balancing. Does it damage iterator operation?
It's not. According to spec - inside Set is more like a List: Set set's [[SetData]] internal slot to a new empty List.
The spec only defines required behavior, not how it should be implemented. Implementations can - for example - achieve the defined behavior by implementing it as a hash set with insertion-ordered linked set entries, thus achieving amortized O(1) lookups while maintaining a predictable order.
@gavenkoa about deleting - it's O(size).
@Bergi, according to spec - Yes: Repeat for each e that is an element of entries, - so one loop over all entries. But, of course, only if it's implemented according to spec.
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.