Question

C++ Iterator Invalidation Rules for Standard Containers

cppiteratorstdc++-faq

Question

What are the iterator invalidation rules for C++ standard library containers?

In particular, when do operations such as insertion, erasure, resizing, reallocation, rehashing, and swapping make existing iterators, references, pointers, or past-the-end iterators unusable?

Short Answer

By the end of this page, you will know what iterator invalidation means, why using an invalid iterator is dangerous, and which common container operations invalidate iterators in C++. You will also learn safe patterns for erasing and inserting elements while iterating.

Concept

An iterator is an object that identifies an element in a container, much like a pointer identifies an object in memory. For example, std::vector<int>::iterator can point to an integer stored in a vector.

An iterator is invalidated when a container operation means that the iterator can no longer safely be used. Dereferencing, incrementing, comparing, or passing an invalid iterator to a container operation can cause undefined behavior.

Invalidation happens because many containers must move elements or rebuild internal storage:

  • A std::vector may allocate a larger contiguous memory block and move every element there.
  • A std::deque may reorganize its internal blocks.
  • An std::unordered_map may rehash its buckets.
  • Removing an element always invalidates iterators that refer to that removed element.

The important detail is that invalidation rules are defined per container and operation. Do not assume that a rule for std::list also applies to std::vector.

Also distinguish these related terms:

  • Iterator validity: whether an iterator may still be used.
  • Reference validity: whether T& ref = container[i] still refers to the same element.
  • Pointer validity: whether T* ptr = &container[i] still points to the same element.
  • Past-the-end iterator: the iterator returned by end(). It does not refer to an element and often becomes invalid after size-changing operations.

Mental Model

Think of a container as a collection of numbered seats.

An iterator is a ticket that says, “I am sitting in this specific seat.” If the venue moves everyone to a new building, your old ticket no longer identifies a usable seat. This is similar to a std::vector reallocation.

If one person leaves a linked-list-style venue, everyone else's seats stay where they are. Only the ticket for the person who left becomes invalid. This is similar to std::list::erase.

Before using an old iterator after changing a container, ask: Did this operation move, remove, or reorganize the element or storage my iterator depends on? If the answer may be yes, obtain a fresh iterator.

Take Quiz

Syntax and Examples

Iterators are commonly obtained with begin(), end(), or a search operation:

#include <vector>

std::vector<int> values{10, 20, 30};
auto it = values.begin(); // Refers to 10

values.push_back(40);

// `it` is valid only if push_back did not reallocate the vector.

For std::vector, appending can require a larger allocation. If that happens, all iterators, references, and pointers to elements are invalidated.

Use reserve() when you know approximately how many elements you will append:

#include <vector>

std::vector<int> values;
values.reserve(10);

values.push_back(10);
auto it = values.begin();

values.push_back(20); // No reallocation while size stays within capacity.
int first = *it;      // Safe here: `it` still refers to 10.

reserve() can prevent future reallocations up to the reserved capacity, but the call to reserve() itself may reallocate and invalidate existing vector iterators.

Step by Step Execution

Consider a vector with no spare capacity:

#include <vector>

std::vector<int> numbers{1, 2, 3};
auto it = numbers.begin();

numbers.push_back(4);

// Do not assume that *it is safe here.

A possible sequence of events is:

  1. numbers stores 1, 2, and 3 in a memory block.
  2. it stores enough information to refer to the first element in that block.
  3. push_back(4) finds that the current block is full.
  4. The vector allocates a larger block elsewhere.
  5. The values are moved or copied into the new block.
  6. The old block is released.
  7. it now refers to storage that is no longer owned by the vector, so it is invalid.

A safe approach is to use a new iterator after the modifying operation:

numbers.push_back(4);
auto first = numbers.begin();
int value = *first; // Safe

Even when a vector does not reallocate, push_back invalidates its old end() iterator because the past-the-end position changes.

Real World Use Cases

Iterator invalidation matters whenever a program keeps a position in a container while modifying that container.

  • Filtering records: remove inactive users from a std::vector<User> without skipping elements or using an invalid iterator.
  • Event queues: append events to a vector while processing a batch. Reserve capacity or avoid retaining iterators across appends.
  • Caches: use std::unordered_map for lookup, while remembering that insertion can trigger a rehash and invalidate iterators.
  • Task schedulers: use std::list when tasks must be removed while iterating and stable iterators are valuable.
  • API design: avoid returning a vector iterator to callers if later API calls may grow, erase from, or replace the vector.
  • Data processing: use the iterator returned by erase to continue a loop safely after removing matching items.
Take Quiz

Real Codebase Usage

In production code, developers reduce invalidation bugs with a few common patterns.

Use the iterator returned by erase

Many containers return the next valid iterator from erase:

#include <vector>

for (auto it = values.begin(); it != values.end(); ) {
    if (*it < 0) {
        it = values.erase(it);
    } else {
        ++it;
    }
}

For a vector, erasing shifts later elements, so the original it cannot be incremented afterward. The returned iterator is the correct continuation point.

Prefer the erase-remove idiom for vectors

When removing many values from a vector, compact first and erase once:

#include <algorithm>
#include <vector>

values.erase(
    std::remove_if(values.begin(), values.end(),
                   [](int value) { return value < 0; }),
    values.end());

Do not retain iterators into values across this operation.

Reserve before bulk appends

std::vector<Result> results;
results.reserve(expectedResultCount);

This reduces reallocations during repeated push_back or emplace_back calls.

Re-find unordered-container elements after possible rehashing

If an insertion may rehash an std::unordered_map, reacquire iterators with find() rather than using an iterator saved before insertion.

Use stable containers deliberately

std::list and associative containers such as std::map preserve iterators to existing elements during insertion. This can be useful, but container choice should also consider memory use, locality, and lookup requirements.

Common Mistakes

Keeping a vector iterator after growth

std::vector<int> values{1, 2, 3};
auto it = values.begin();
values.push_back(4);
std::cout << *it; // Potentially undefined behavior

push_back may reallocate. Reserve enough capacity before saving the iterator, or reacquire it afterward.

Incrementing an iterator after erasing it

for (auto it = values.begin(); it != values.end(); ++it) {
    if (*it == 0) {
        values.erase(it); // `it` is invalid now
    }
}

Use the iterator returned by erase instead.

Assuming end() remains valid

auto last = values.end();
values.push_back(42);

if (values.end() == last) {
    // Do not rely on this comparison.
}

For most size-changing operations, reacquire end() in the loop condition rather than caching it.

Confusing references with iterators in unordered containers

Insertion into an std::unordered_map may invalidate iterators when a rehash occurs, but references and pointers to existing elements remain valid unless that element is erased. These are different guarantees.

Assuming reserve() makes all vector modifications safe

reserve() prevents reallocation only until the vector exceeds its capacity. Inserting or erasing in the middle still invalidates iterators at and after the changed position, even when no reallocation occurs.

Using an iterator after swap

After swapping containers, iterators to elements generally continue to refer to those elements, but the elements now belong to the other container. Past-the-end iterators should be reacquired. Do not pass an old iterator to the original container after a swap.

Comparisons

ContainerInsertionErasureNotes
std::vectorReallocation invalidates all iterators, references, and pointers. Without reallocation, insertion invalidates iterators at or after the insertion point.Invalidates iterators, references, and pointers at or after the erased position.end() is invalidated by size changes.
std::dequeInsertion invalidates iterators. References and pointers to existing elements remain valid for insertion at either end, but do not assume iterator stability.Erasing at an end invalidates only iterators/references to erased elements; erasing in the middle invalidates all iterators and references.Deque iterator rules are less intuitive than vector rules.
std::listExisting iterators, references, and pointers remain valid.Only iterators, references, and pointers to erased elements are invalidated.Nodes are individually allocated and linked.
std::forward_listExisting iterators, references, and pointers remain valid.Only those referring to erased elements are invalidated.Singly linked list; erasure is commonly performed with erase_after.
std::map / std::setExisting iterators, references, and pointers remain valid.Only those referring to erased elements are invalidated.Ordered associative containers.
std::unordered_map / std::unordered_setA rehash invalidates all iterators. Without a rehash, existing iterators remain valid. References and pointers to existing elements remain valid.Only iterators, references, and pointers to erased elements are invalidated.reserve() and rehash() invalidate iterators.
std::arrayIts size cannot change.Its size cannot change.Iterators remain valid while the array object exists; replacing the array object is different.

vector versus list

  • Choose vector for compact storage, fast indexing, and efficient iteration. Plan for reallocation and shifted elements.
  • Choose list only when stable iterators and frequent insertion/erasure at known positions are more important than contiguous storage and cache locality.

map versus unordered_map

  • map keeps iterators stable across insertion and provides sorted keys.
  • unordered_map usually offers faster average lookup, but insertion can rehash and invalidate iterators.
Take Quiz

Cheat Sheet

  • An invalid iterator must not be dereferenced, incremented, compared, or passed to container functions.
  • Removing an element always invalidates iterators, references, and pointers to that element.
  • std::vector reallocation invalidates everything into the vector.
  • Vector insertion/erasure without reallocation invalidates positions at or after the changed position.
  • Vector push_back invalidates the old end() iterator even if no reallocation happens.
  • std::list, std::forward_list, std::map, and std::set keep iterators to other elements valid when inserting or erasing.
  • std::unordered_map insertion can invalidate iterators if it causes a rehash; references and pointers to existing elements survive rehashing.
  • rehash() and reserve() on unordered containers invalidate iterators.
  • Prefer it = container.erase(it) when erasing during iteration.
  • Reacquire begin() or end() after a container-changing operation when unsure.
  • After swap, element iterators refer to elements now owned by the other container; reacquire end().
  • Check the documentation for the exact container member function when correctness depends on invalidation guarantees.

FAQ

What does iterator invalidation mean in C++?

It means an iterator no longer safely refers to a usable position in its container. Using it afterward can cause undefined behavior.

Does std::vector::push_back invalidate iterators?

It invalidates all iterators, references, and pointers if it reallocates. Without reallocation, iterators to existing elements remain valid, but the previous end() iterator is invalidated.

Does std::vector::erase invalidate every iterator?

No. It invalidates iterators, references, and pointers at or after the erased position. Iterators before that position remain valid.

Which C++ containers have stable iterators during insertion?

std::list, std::forward_list, std::map, and std::set keep iterators to existing elements valid during insertion.

Does std::unordered_map::insert invalidate iterators?

It can. If insertion causes a rehash, all iterators are invalidated. References and pointers to existing elements remain valid unless their element is erased.

Can reserve() prevent vector iterator invalidation?

It can prevent invalidation caused by future vector reallocations while the size remains within capacity. It does not prevent invalidation caused by inserting or erasing in the middle.

Is it safe to erase elements while iterating?

Yes, when you follow the container's erase rules. Usually, assign the iterator returned by erase back to the loop iterator.

Are pointers and references invalidated exactly like iterators?

Not always. For example, unordered-container rehashing invalidates iterators but does not invalidate references or pointers to existing elements. Check the specific container rule.

Take Quiz

Mini Project

Description

Build a small cleanup function for a list of integer measurements. The function removes invalid negative measurements while iterating over a std::vector. This demonstrates the safe erase-loop pattern and why the return value from erase matters.

Goal

Create a function that removes all negative integers from a vector without using an invalidated iterator.

Requirements

Use a std::vector<int> containing both positive and negative values.

Remove every negative value while traversing the vector.

Use the iterator returned by vector::erase after an element is removed.

Print the remaining values after cleanup.

Take Quiz

Keep learning

Related questions

View more