Skip to main content
STL Containers

The Standard Template Library (STL)

The STL is C++‘s superpower. It provides efficient, tested, and reusable components. Rule #1 of C++: If it’s in the STL, don’t write it yourself. Why write a buggy linked list when std::list exists and is optimized by experts?

1. Containers

Containers are data structures that store collections of objects. They manage memory automatically (using RAII internally), so you never need to new/delete when using them. Picking the right container is one of the most impactful performance decisions you will make in C++.

Sequential Containers (Ordered)

std::vector (Dynamic Array)
  • What is it?: A contiguous, dynamically-growing array. Elements are stored side-by-side in memory, which means the CPU cache can prefetch them efficiently.
  • Use by default: 99% of the time, vector is the right choice. Even for small collections where you might think a linked list is “theoretically better,” vector wins because of cache locality. Bjarne Stroustrup himself has demonstrated this in benchmarks.
  • Operations: O(1) random access ([]), O(1) amortized push_back, O(n) insert in the middle.
Common pitfall — iterator invalidation: When a vector grows beyond its capacity, it allocates a new, larger block and moves all elements. This invalidates every pointer, reference, and iterator to its elements. Never hold an iterator across a push_back that might trigger a reallocation. If you need stable references, consider std::deque or std::list.
std::array (Fixed-Size Array)
  • What is it?: A safer version of C-style arrays (int arr[5]).
  • Use when: Size is known at compile time and won’t change.
  • Zero overhead: No dynamic allocation.

Associative Containers (Key-Value / Sorted)

std::map (Ordered Map)
  • What is it?: A tree-based dictionary.
  • Order: Keys are always sorted (alphabetically or numerically).
  • Complexity: O(log n).

Unordered Containers (Hash Tables)

std::unordered_map
  • What is it?: A hash table. Keys are hashed into buckets for near-instant lookup.
  • Order: No guaranteed order. Elements may appear in any sequence when iterating.
  • Complexity: O(1) average for insert, find, and erase. O(n) worst case if many keys hash to the same bucket (hash collision).
  • Use when: You need fast lookups and do not care about iteration order. This is the right choice for caches, memoization tables, and symbol tables.

Container Performance Comparison

Container Selection Quick Guide

Choosing the right container matters. Here is a practical decision tree:
  • Need a sequence of elements? Use std::vector (default) or std::deque (fast insert at front).
  • Need key-value pairs with sorted keys? Use std::map.
  • Need key-value pairs with fast lookup? Use std::unordered_map.
  • Need to ensure no duplicates? Use std::set or std::unordered_set.
  • Need a fixed-size array? Use std::array.
  • Need a FIFO queue? Use std::queue (adapter over std::deque).
  • Need a priority queue? Use std::priority_queue.
The vector almost always wins in practice: Even for operations where list has better theoretical complexity (O(1) insert vs O(n)), std::vector is often faster for collections under ~10,000 elements because of CPU cache effects. Contiguous memory means the CPU prefetcher loads the next elements before you ask for them. Linked list nodes are scattered across the heap, causing cache misses on every traversal. Bjarne Stroustrup’s benchmarks show vector beating list for insert-in-the-middle operations up to surprisingly large sizes. Default to vector and switch only when profiling proves otherwise.

When to Reach for Something Other Than vector


2. Iterators

Iterators are the glue between Containers and Algorithms. Think of them as a universal “cursor” that knows how to move through any container, regardless of its internal structure. A vector iterator moves through contiguous memory; a map iterator walks a balanced tree. But the interface is the same — that is the power of abstraction.
  • begin(): Points to the first element.
  • end(): Points to one past the last element (this is a sentinel, not a valid element).

Range-Based For Loop

This syntactic sugar uses iterators behind the scenes but is much cleaner. Prefer this in all new code.
Why does end() point past the last element? This “half-open range” convention [begin, end) makes empty ranges natural (begin == end), makes computing size trivial (end - begin), and makes chaining ranges easy. It is used consistently throughout the STL and is one of those design decisions that seems odd at first but proves elegant in practice.

3. Algorithms

The <algorithm> header contains 100+ functions for sorting, searching, and manipulating ranges. They are often faster and less buggy than writing your own loops.

Sorting & Searching

Transformations

Finding


4. Lambdas (Anonymous Functions)

Lambdas allow you to write small, throwaway functions inline. They are perfect for passing to algorithms like sort or find_if. Before C++11, you had to write separate functor classes for this — lambdas eliminated that boilerplate entirely. Syntax: [capture](parameters) -> return_type { body }

Capture Clauses

  • []: No capture. The lambda cannot access any variables from the enclosing scope. This is the safest option.
  • [=]: Capture all local variables by value (copy). The lambda gets its own snapshot.
  • [&]: Capture all local variables by reference (can modify originals).
  • [x]: Capture only x by value. Prefer explicit captures — they make dependencies clear.
  • [&x]: Capture only x by reference.
  • [=, &x]: Capture everything by value, except x by reference.
Dangling reference pitfall: If you capture a local variable by reference ([&]) and the lambda outlives the variable (e.g., you return the lambda or store it), the reference dangles and accessing it is undefined behavior. Capture by value when the lambda might outlive the enclosing scope. This is especially dangerous with std::async and std::thread.

Summary

  • Containers: Use std::vector by default. Use std::map for sorted keys, std::unordered_map for speed.
  • Iterators: Abstract the navigation of data.
  • Algorithms: Prefer std::sort, std::find, etc., over writing raw loops.
  • Lambdas: Write concise, local functions for algorithms.
Next, we’ll look at the cutting edge: Modern C++ (C++11 to C++20) features.