What happens if you modify a collection while looping over it?

A dict raises RuntimeError: dictionary changed size during iteration. A list does something worse: it silently skips elements, because removing one shifts everything left while the index keeps advancing. Iterate over a copy, or build a new collection.

Overview

What goes wrong

Removing items from a list while looping over it silently skips elements. It does not raise, which is what makes it dangerous.

1Python
Output

The mechanism: list iteration is driven by an internal index. Removing element 1 shifts everything down one slot, and the index still advances — so the element that moved into the vacated position is never examined.

StepIndexListSeesAction
10[1, 2, 2, 3]1keep
21[1, 2, 2, 3]2remove → [1, 2, 3]
32[1, 2, 3]3keep — the second 2 was skipped

The list is now [1, 2, 3] and the loop is finished.

Dictionaries and sets behave differently — they raise:

2Python
Output

Which is far better, because the failure is loud. Dicts and sets track a version counter and detect the change. Lists do not, so they fail quietly instead.

CollectionMutating during iteration
listSilently skips elements
dictRuntimeError on size change
setRuntimeError on size change
dequeRuntimeError
GeneratorDepends on the source

Note the dict wording: size change. Reassigning an existing key's value is allowed and safe; adding or deleting keys is not.

Dicts, sets & hashingConceptualEasy

Step through it

What to watch

  • The dict records its size and checks it on every step.
  • The error names the real cause — a size change.
  • Rebuilding rather than mutating avoids the question entirely.

Say this out loud

"Dicts raise RuntimeError. Lists don't - they silently skip elements, because deleting shifts the rest left while the index moves right. I iterate over list(d) or a slice copy, or better, build a new collection with a comprehension."

What happens if you modify a collection while looping over it?

Why can't you delete from a dict or a list while iterating it, and what should you do instead?

Run it

The dict raising, the list silently skipping on the same logical operation, and the three fixes all producing the answer the broken loop was supposed to give.

3Python
Output

The four correct approaches

Build a new collection — clearest, and the default choice:

4Python
Output

Iterate over a copy when the original object must be mutated in place, because other references point at it:

5Python
Output

list(d) is the idiom for dictionaries — it materialises the keys before the loop starts, so deletions during the loop cannot disturb it.

Slice-assign to mutate in place with no copy semantics visible to callers:

6Python
Output

This is the underused one. It keeps the same list object — so aliases see the change — while doing the filtering safely.

Iterate backwards when removing by index, since removals then only affect positions already visited:

7Python
Output

Two different failures

A dictionary keeps a version counter and compares it on each step, so a size change during iteration is caught immediately and loudly. That is a feature: the alternative is undefined behaviour, because a resize can move every entry to a different slot.

A list has no such check. Deleting element i shifts everything after it one place left, while the loop's internal index advances — so the element that moved into position i is never visited. You get a wrong answer and no error at all, which is the harder bug to find.

The fixes, in order of preference

Build a new collection. [x for x in items if keep(x)] or {k: v for k, v in d.items() if keep(k)}. Nothing is mutated, the intent is explicit, and it is usually faster than repeated deletion.

Iterate over a snapshot. for k in list(d): or for x in items[:]:. Necessary when you genuinely must mutate in place — because other references to the object are watching.

Filter in place backwards. Walking from the end means deletions only shift elements you have already passed. Correct, and worth knowing for when memory matters.

What counts as a change

For a dict, only a size change trips the check. Assigning to an existing key is fine, which surprises people who expect any mutation to raise. Adding a key raises just as deletion does.

Sets behave like dicts. collections.deque also raises. And modifying the object a variable points to — appending to a list stored as a dict value — is not a change to the dict, so it is perfectly legal.

Which to choose

SituationApproach
Filtering, no aliasing concernsComprehension — nums = [...]
Must keep the same objectSlice assignment — nums[:] = [...]
Removing by indexIterate backwards
Dictionary keysfor k in list(d)
Very large collection, memory-boundIn-place two-pointer compaction
Adding while iteratingCollect additions, extend after the loop

The aliasing distinction is the one that causes real bugs:

8Python
Output
9Python
Output

If a function is documented as modifying its argument, the comprehension form silently does nothing from the caller's perspective. Slice assignment is what actually mutates.

For the memory-bound case, two-pointer compaction filters in place with no second list:

10Python
Output

O(n) time, O(1) extra space — the same pattern as "remove duplicates in place".

Adding during iteration

Growing a list while iterating it does not raise either — and it can loop forever:

11Python
Output

The index keeps finding new elements. The fix is to accumulate separately:

12Python
Output

The same discipline applies to a worklist algorithm, where the natural structure is an explicit queue rather than a for loop:

13Python
Output

That is the correct shape for BFS, dependency resolution and crawling — anything where processing an item produces more items.

Questions people ask

Why does a list not raise like a dict? Lists have no version counter; iteration is a plain index. Dicts and sets track modifications because rehashing could otherwise corrupt the traversal.

Is nums[:] a deep copy? No, shallow — the new list holds the same element references. Fine for filtering, not for mutating the elements themselves.

Can I change dict values while iterating? Yes. Only size changes raise.

What about for k, v in d.items() and then del? Same error. Wrap in list(d.items()).

Is iterating backwards a hack? No — it is correct and O(n), and it is the right choice when removing by index in place.

What is fastest? A comprehension, usually — one pass, one allocation, and the loop runs in C.

Does this apply to generators? A generator over a mutating source gives undefined results; materialise it first if the source will change.

Recap in one screen

  • Removing from a list while iterating silently skips elements, because the index advances past the shifted-down item.
  • Dicts and sets raise RuntimeError on a size change — a loud failure, which is better.
  • Prefer a comprehension; use nums[:] = [...] when the caller's object must be mutated.
  • For dictionaries, iterate list(d) to snapshot the keys first.
  • Growing a list while iterating it can loop forever — collect additions and extend afterwards, or use an explicit worklist.

How the code works

The dict raising, the list silently skipping on the same logical operation, and the three fixes all producing the answer the broken loop was supposed to give.

How the code works

  1. del d[key] inside for key in dRaises immediately. The dict compares its recorded size on every step, because a resize can move every entry and iteration would otherwise be undefined.
  2. broken.remove(x) inside for x in brokenNo error, and 6 survives. Removing 4 shifts 6 into the slot the loop has already passed, so it is never examined — the silent failure is the dangerous one.
  3. [x for x in items if x % 2]The fix to reach for first. Nothing is mutated, the intent is visible, and it is usually faster than repeated remove, which is O(n) each.
  4. nested[key].append(0)Legal. Mutating a value does not change the dictionary's size or its keys, so the version check never fires.

Change one thing

  • Add a key inside the loop instead of deleting one. Same RuntimeError — it is the size that matters, not the direction.
  • Try the same removal on a set. It raises like the dict, which is a good reminder that only the list fails quietly.

Where this runs

Real CPython, compiled to WebAssembly and running on your own machine — nothing is uploaded. The first run takes a few seconds while the interpreter downloads; after that it is immediate. Need more room, or want to paste your own attempt? Use the Python compiler.

Check yourself

0 of 3

Answer without scrolling back up.

  1. Deleting from a list while iterating over it:

  2. Which dict operation during iteration is legal?

  3. The preferred fix is:

Cheat sheet

What happens if you modify a collection while looping over it?

A dict raises RuntimeError: dictionary changed size during iteration. A list does something worse: it silently skips elements, because removing one shifts everything left while the index keeps advancing. Iterate over a copy, or build a new collection.

INTERVIEW · vizlearn.in/interview/modifying-a-collection-while-iterating.html

About the author

Ashish Jangra builds and maintains VizLearn. Every module here is written and the visualisation behind it hand-built, so the numbers in a readout come from the same code that draws the picture. Corrections are genuinely welcome and get priority over everything else — if a page states something wrong, or an animation misrepresents what the algorithm does, get in touch.