STL Containers

vector, list, deque, map, set

Read: ~45 min 22 practice problems

1. The Container Family

ContainerOrderLookupInsert
vectorInsertion (contiguous)O(1) by indexO(1) amortized at end
dequeInsertionO(1) by indexO(1) at both ends
listInsertion (linked)O(n)O(1) anywhere with iter
setSortedO(log n)O(log n)
mapSorted by keyO(log n)O(log n)
unordered_setHashedO(1) avgO(1) avg
unordered_mapHashed by keyO(1) avgO(1) avg

2. std::vector — The Default

vec.cpp
#include <iostream>
#include <vector>

int main() {
    std::vector<int> v = {3, 1, 4, 1, 5, 9};
    v.push_back(2);
    v.insert(v.begin() + 2, 99);
    v.erase(v.begin());
    for (int x : v) std::cout << x << " ";
    std::cout << "\n";
}

3. std::map & std::unordered_map

map.cpp
#include <iostream>
#include <map>
#include <unordered_map>
#include <string>

int main() {
    std::map<std::string, int> ages;        // sorted, O(log n)
    ages["sara"]  = 21;
    ages["karim"] = 25;
    ages["ali"]   = 30;

    for (const auto& [name, age] : ages)
        std::cout << name << " -> " << age << "\n";

    std::unordered_map<std::string, int> um; // hashed, O(1) avg
    um["x"] = 1;
    if (auto it = um.find("x"); it != um.end())
        std::cout << it->second << "\n";
}

4. std::set — Unique, Sorted

set.cpp
#include <iostream>
#include <set>

int main() {
    std::set<int> s = {3, 1, 4, 1, 5}; // dupes removed
    s.insert(2);
    for (int x : s) std::cout << x << " "; // 1 2 3 4 5
}

5. Container Adaptors

Built on top of containers, restricting the interface:

  • std::stack<T> — LIFO (default backing: deque)
  • std::queue<T> — FIFO (default: deque)
  • std::priority_queue<T> — heap-based, max at top

6. Choosing the Right Container

Decision tree
  1. Need key→value lookup? → unordered_map (or map if order matters)
  2. Need uniqueness? → unordered_set / set
  3. Need fast insert at both ends? → deque
  4. Otherwise → vector

Almost never use std::list — its constant overhead and cache misses outweigh the O(1) middle-insert in practice.

7. Practice Problems

  1. Count word frequencies with map.
    ✨ Show Answer
    std::map<std::string, int> freq;
    for (const auto& w : words) ++freq[w];
  2. Why prefer unordered_map for huge data?
    ✨ Show Answer

    O(1) average lookup vs map's O(log n). For 1M entries, that's 1 step vs 20. Use ordered map only when iteration order matters.

  3. Find unique elements in a vector.
    ✨ Show Answer
    std::set<int> uniq(v.begin(), v.end());
  4. Push to a stack and pop.
    ✨ Show Answer
    std::stack<int> s;
    s.push(1); s.push(2);
    std::cout << s.top(); s.pop();
  5. Why is iteration on map slower than on vector?
    ✨ Show Answer

    Map is a tree with scattered nodes — cache misses every step. Vector is contiguous — prefetcher loves it.

  6. Sort a vector ascending.
    ✨ Show Answer
    std::sort(v.begin(), v.end());
  7. Sort descending.
    ✨ Show Answer
    std::sort(v.begin(), v.end(), std::greater<>{});
  8. Iterator invalidation: when does push_back invalidate iterators?
    ✨ Show Answer

    If reallocation happens (size exceeds capacity), all iterators are invalidated. Reserve enough up front to avoid this.

  9. Build a priority_queue of pairs (priority, item).
    ✨ Show Answer
    std::priority_queue<std::pair<int, std::string>> pq;
    pq.push({5, "high"});
    pq.push({1, "low"});
    std::cout << pq.top().second; // high
  10. Difference between map[k] and map.find(k)?
    ✨ Show Answer

    m[k] inserts a default value if key missing. m.find(k) returns an iterator (or end()) without modifying the map.

  11. Min-heap from priority_queue.
    ✨ Show Answer
    std::priority_queue<int, std::vector<int>, std::greater<>> minHeap;
  12. Reserve capacity 1M before push_back loop. Why?
    ✨ Show Answer

    Avoids many reallocations and copies as size grows. Big win for large vectors.

  13. Erase even numbers from a vector.
    ✨ Show Answer
    v.erase(std::remove_if(v.begin(), v.end(),
        [](int x){ return x % 2 == 0; }), v.end());

    (C++20: std::erase_if(v, ...).)

  14. Convert vector to set in one line.
    ✨ Show Answer
    std::set<int> s(v.begin(), v.end());
  15. Why almost never use std::list?
    ✨ Show Answer

    Cache-unfriendly. Even with O(1) middle insert, a vector with shifting is faster on small/medium data due to memory locality.

  16. Iterate map by keys only.
    ✨ Show Answer
    for (const auto& [k, _] : m) std::cout << k;
  17. Insert only if absent.
    ✨ Show Answer
    auto [it, inserted] = m.insert({"a", 1});
  18. Sum of all map values.
    ✨ Show Answer
    int total = 0;
    for (const auto& [k, v] : m) total += v;
  19. Group elements by parity in two vectors.
    ✨ Show Answer
    std::vector<int> even, odd;
    for (int x : v) (x % 2 ? odd : even).push_back(x);
  20. Find median of vector.
    ✨ Show Answer
    std::nth_element(v.begin(), v.begin() + v.size()/2, v.end());
    int median = v[v.size()/2];
  21. unordered_map of int → vector<int> (adjacency list).
    ✨ Show Answer
    std::unordered_map<int, std::vector<int>> graph;
    graph[1].push_back(2);
    graph[1].push_back(3);
  22. When does std::map's iterator stay valid?
    ✨ Show Answer

    Always, except for iterators to erased elements. Tree structure means insert/erase don't shift the rest. (Vector is the opposite.)

Summary

std::vector is the default. std::unordered_map for fast lookup; std::map when iteration order matters. std::set for uniqueness. Container adaptors (stack, queue, priority_queue) wrap a sequence container. Iterator invalidation is the #1 STL bug — be aware of which operations invalidate which iterators.

Next Module → STL Algorithms: sort, find, transform, accumulate.