List iterator vs. vector iterator
Categories:
C++ Iterators: std::list vs. std::vector

Explore the fundamental differences between iterators for std::list
and std::vector
in C++, understanding their performance implications and use cases.
In C++, iterators are a powerful concept that provides a uniform way to access elements in various container types. While they offer a common interface, the underlying implementation and behavior of iterators can differ significantly based on the container. This article delves into the distinctions between iterators for std::list
and std::vector
, two of the most commonly used containers in the Standard Template Library (STL). Understanding these differences is crucial for writing efficient and robust C++ code.
Understanding std::vector Iterators
std::vector
is a sequence container that encapsulates dynamic size arrays. Its elements are stored in contiguous memory locations, similar to a raw C-style array. This contiguous storage is the key characteristic that defines the behavior of std::vector
iterators.
graph TD A[std::vector] --> B[Contiguous Memory] B --> C[Random Access Iterators] C --> D["O(1) Element Access"] C --> E["O(N) Insert/Delete (Middle)"] D --> F["Pointer-like Behavior"] E --> G["Invalidation on Resize/Insert/Delete"]
Characteristics of std::vector
and its iterators.
Because std::vector
elements are stored contiguously, its iterators are random access iterators. This means you can perform arithmetic operations on them (e.g., it + 5
, it - 2
) and directly jump to any element in constant time, just like with pointers. This makes std::vector
ideal for scenarios requiring fast random access to elements.
#include <vector>
#include <iostream>
int main() {
std::vector<int> vec = {10, 20, 30, 40, 50};
// Random access using iterator arithmetic
auto it = vec.begin();
std::cout << "First element: " << *it << std::endl; // 10
it += 2; // Move 2 positions forward
std::cout << "Element after moving 2 positions: " << *it << std::endl; // 30
// Direct access using operator[]
std::cout << "Element at index 3: " << vec[3] << std::endl; // 40
return 0;
}
Demonstrating random access with std::vector
iterators.
std::vector
iterators is their invalidation. Operations like push_back
(if it causes reallocation), insert
, or erase
can invalidate existing iterators and references to elements. This is because these operations might reallocate the vector's memory or shift elements, making previous iterator positions incorrect.Understanding std::list Iterators
std::list
is a sequence container that implements a doubly linked list. Unlike std::vector
, its elements are not stored contiguously in memory. Instead, each element is stored in a separate node that contains the element's value and pointers to the previous and next nodes in the sequence.
graph TD A[std::list] --> B[Non-Contiguous Memory] B --> C[Bidirectional Iterators] C --> D["O(N) Element Access (by index)"] C --> E["O(1) Insert/Delete (Known Position)"] D --> F["No Pointer Arithmetic"] E --> G["No Invalidation on Insert/Delete (except deleted element)"]
Characteristics of std::list
and its iterators.
Due to its linked structure, std::list
iterators are bidirectional iterators. This means you can only move them one step at a time, either forward (++it
) or backward (--it
). You cannot perform arbitrary arithmetic operations like it + 5
because there's no guarantee of contiguous memory. Accessing an element by its index (e.g., the 5th element) requires traversing the list from the beginning, resulting in O(N) time complexity.
#include <list>
#include <iostream>
int main() {
std::list<int> lst = {10, 20, 30, 40, 50};
auto it = lst.begin();
std::cout << "First element: " << *it << std::endl; // 10
// Move forward one by one
++it;
std::cout << "Element after ++it: " << *it << std::endl; // 20
// Move backward one by one
--it;
std::cout << "Element after --it: " << *it << std::endl; // 10
// This would be a compile-time error: it += 2;
return 0;
}
Demonstrating bidirectional access with std::list
iterators.
std::list
is its efficient insertion and deletion of elements anywhere in the list (O(1) complexity), provided you have an iterator pointing to the insertion/deletion point. Furthermore, std::list
iterators are generally more stable; they are not invalidated by insertions or deletions of other elements, only by the deletion of the element they point to.Key Differences and Performance Implications
The fundamental difference in memory layout leads to distinct performance characteristics and use cases for std::vector
and std::list
iterators.

Comparison of std::vector
vs. std::list
Iterator Characteristics.
When choosing between std::vector
and std::list
, consider the following:
- Random Access: If you frequently need to access elements by index or jump around the collection,
std::vector
is superior due to its random access iterators and contiguous memory. - Insertions/Deletions: If you frequently insert or delete elements in the middle of the collection,
std::list
is more efficient (O(1)) once you have an iterator to the position.std::vector
can be O(N) for these operations as it might require shifting many elements. - Iterator Stability: If you need iterators to remain valid across insertions and deletions (of other elements),
std::list
is the safer choice.std::vector
iterators are easily invalidated. - Memory Overhead:
std::list
typically has higher memory overhead per element due to storing pointers to the next and previous nodes.
std::vector
is often the default choice due to better cache performance and lower memory overhead. std::list
shines in specific scenarios requiring frequent middle insertions/deletions and stable iterators.