C++ for Coding Interviews: The STL Toolkit That Actually Matters

- C++ for coding interviews lives or dies on STL fluency:
unordered_mapvsmap, min-heap syntax, and safe iteration are the recurring gaps. unordered_mapgives O(1) average lookup; reach formaponly when you needlower_bound,upper_bound, or sorted key iteration.- Min-heap syntax is
priority_queue<int, vector<int>, greater<int>>; custom comparators require a struct, not a lambda. - Iterator invalidation after
vector::eraseis undefined behavior; always capture the iteratorerase()returns. multiset::erase(value)removes every copy; usems.erase(ms.find(value))to drop a single occurrence in sliding window problems.- Comparators must satisfy strict weak ordering: returning
truefor equal elements is undefined behavior instd::sortandpriority_queue. __gnu_pbds::treegives you an order-statistics tree withfind_by_orderandorder_of_keyon GCC without any custom implementation.
Most C++ developers walk into a coding interview knowing the language well and the standard library only sort of. That gap kills more interviews than algorithm gaps do.
The real question is fluency, not capability. C++ can express any algorithm, but you are fighting the language every time you forget a template syntax or spend three minutes debugging a comparator. An interviewer watching you wrestle with greater<int> for three minutes is writing exactly one thing in their notes.
Should You Even Use C++?
Use C++ if you have been writing it for at least a year, or if you are targeting a role where it is the production language (HFT, systems software, game engines, NVIDIA, Qualcomm, embedded shops). Otherwise, Python is faster to write and just as correct.
C++ wins when you need it:
- Roles where the interviewer is a C++ engineer and wants idiomatic code
- Problems that need bitwise manipulation at the word level
- Situations where you want
multiset(no clean stdlib equivalent in Python or Java)
C++ loses when you do not know it cold. If you hesitate on how to initialize a priority_queue with a custom comparator, switch languages before the interview. The STL is powerful but unforgiving. One wrong template argument and you are staring at three pages of error output that mention a type you have never seen before.
See Best Language for Coding Interviews for the full decision framework.
Which Container Do You Actually Need?
| Container | Underlying structure | Lookup | Insert/Delete | Ordered? |
|---|---|---|---|---|
vector<T> | Dynamic array | O(1) index | O(1) amortized end, O(n) middle | No |
unordered_map<K,V> | Hash table | O(1) avg | O(1) avg | No |
map<K,V> | Red-black tree | O(log n) | O(log n) | Yes |
unordered_set<T> | Hash table | O(1) avg | O(1) avg | No |
set<T> | Red-black tree | O(log n) | O(log n) | Yes |
multiset<T> | Red-black tree | O(log n) | O(log n) | Yes, duplicates allowed |
priority_queue<T> | Binary heap | O(1) top | O(log n) push/pop | Partial (max at top) |
deque<T> | Chunked array | O(1) front/back | O(1) front/back | No |
stack<T> | deque adapter | O(1) top | O(1) | No |
queue<T> | deque adapter | O(1) front | O(1) | No |
One Structure, Every Interview Pattern
vector Is Your Default Array
#include <vector> #include <algorithm> vector<int> v = {3, 1, 4, 1, 5}; v.push_back(9); // O(1) amortized v.reserve(100); // pre-allocate to avoid reallocation sort(v.begin(), v.end()); // in-place, O(n log n) v.erase(v.begin() + 2); // O(n) shift
reserve() before bulk pushes. Without it, the vector reallocates at capacity and copies every element.
unordered_map for Counting, Not map
#include <unordered_map> unordered_map<string, int> freq; for (const string& word : words) { freq[word]++; // inserts 0 if missing, then increments } if (freq.count("foo")) { /* exists */ } if (freq.find("foo") != freq.end()) { /* also fine */ }
Use unordered_map (O(1)) for frequency counting and map (O(log n)) only when you need ordered iteration or lower_bound. Swapping them is a complexity error, not a correctness error, and interviewers notice.
map When Order Matters
#include <map> map<int, int> m; m[5] = 10; m[3] = 7; // lower_bound and upper_bound: critical for interval and range problems auto it = m.lower_bound(4); // first key >= 4, points to {5, 10} auto it2 = m.upper_bound(4); // first key > 4, same result here
lower_bound and upper_bound are the reason to reach for map over unordered_map. If a problem involves finding the nearest key or iterating in sorted key order, the binary-search methods are already built in.
set and multiset: Sorted Membership
#include <set> set<int> s = {3, 1, 4, 1, 5}; // {1, 3, 4, 5}: duplicates dropped multiset<int> ms = {3, 1, 4, 1, 5}; // {1, 1, 3, 4, 5}: duplicates kept ms.erase(ms.find(1)); // removes ONE copy of 1 ms.erase(1); // removes ALL copies of 1 -- often a bug
multiset is the closest C++ comes to Python's SortedList. It shows up in sliding window median problems where you need a sorted window with element-level removal. No direct equivalent in Java without manual TreeMap count tracking.
priority_queue: Max-Heap by Default, Min-Heap Requires an Incantation
#include <queue> // max-heap (default) priority_queue<int> maxPQ; maxPQ.push(3); maxPQ.push(1); maxPQ.push(4); maxPQ.top(); // 4 // min-heap: requires this exact syntax priority_queue<int, vector<int>, greater<int>> minPQ; // custom comparator: lambda does NOT work directly; use a struct struct Cmp { bool operator()(const pair<int,int>& a, const pair<int,int>& b) { return a.second > b.second; // min-heap by second element } }; priority_queue<pair<int,int>, vector<pair<int,int>>, Cmp> customPQ; // range constructor beats pushing one by one vector<int> data = {5, 3, 8, 1, 9}; priority_queue<int, vector<int>, greater<int>> minPQ2(data.begin(), data.end());
The default is max-heap. Min-heap requires greater<int> as the third template argument. Nobody intuitively reads "greater" and thinks "this gives me the smallest element first." The name means "use greater-than for comparison," which flips the heap ordering. Just memorize the incantation and move on.

The C++ standard committee, deciding that greater<int> for a min-heap is perfectly intuitive
See The Heap Data Structure for why the heap shape guarantees O(log n) push and pop.
Five Traps That Bite Mid-Interview
1. Iterator Invalidation After erase
vector<int> v = {1, 2, 3, 4, 5}; // WRONG: it is invalid after erase; undefined behavior for (auto it = v.begin(); it != v.end(); ++it) { if (*it % 2 == 0) v.erase(it); } // CORRECT: erase returns the next valid iterator for (auto it = v.begin(); it != v.end();) { if (*it % 2 == 0) it = v.erase(it); else ++it; }
Any erasure from a vector invalidates iterators at and after the erased position. Any reallocation invalidates all of them. The compiler will not warn you. The code compiles, runs, and produces wrong results at a timing that is impossible to predict. If you erase while iterating without capturing the return value, you are just hoping.
2. operator[] on map Inserts
map<string, int> m; int val = m["key"]; // m now has {"key": 0} -- inserted a phantom entry // Safe reads if (m.count("key")) { int val = m["key"]; } auto it = m.find("key"); if (it != m.end()) { int val = it->second; }
The program compiles and runs. Your map just quietly accumulates phantom zero entries. In a frequency problem, every freq[word] for a word you check but never insert inflates your map and corrupts size checks. The bug never announces itself.
3. Comparators Must Be Strict Weak Ordering
// WRONG: returns true for equal elements, causes undefined behavior in sort sort(v.begin(), v.end(), [](int a, int b) { return a <= b; }); // WRONG: subtraction overflows silently on large values sort(pairs.begin(), pairs.end(), [](auto& a, auto& b) { return a.second - b.second; }); // CORRECT sort(pairs.begin(), pairs.end(), [](auto& a, auto& b) { return a.second < b.second; });
A comparator returning true for equal elements causes undefined behavior in std::sort. The same rule applies to priority_queue comparators. The integer subtraction trick from C-style qsort does not belong in C++ and overflows silently on large values.

You, fifteen minutes into debugging why your sort produces a different wrong answer every run
4. multiset::erase(value) Removes All Copies
ms.erase(1) removes every 1. In a sliding window where you need to drop one occurrence, use ms.erase(ms.find(value)). The value-based overload is the trap, and it compiles without a word of complaint. You will not notice until your window has the wrong size and your solution fails every test case that contains duplicates.
5. auto in Range-For Copies, auto& Does Not
map<string, int> m = {{"a", 1}, {"b", 2}}; for (auto kv : m) { kv.second += 1; } // m unchanged, copies every pair for (auto& [key, val] : m) { val += 1; } // m modified, zero copies
Structured bindings (auto& [key, val]) require C++17. LeetCode and most interview platforms compile at C++17 or C++20, so this is safe.
What C++ Gives You That Others Do Not
lower_bound and upper_bound work on any sorted range: set, map, and multiset as member functions, and any sorted sequence as free functions.
vector<int> sorted = {1, 3, 5, 7, 9}; auto it = lower_bound(sorted.begin(), sorted.end(), 6); int idx = it - sorted.begin(); // 3, pointing to 7
Use this instead of writing your own binary search. The stdlib implementation is correct.
The policy-based data structure (__gnu_pbds::tree) gives you an order-statistics tree with find_by_order and order_of_key in one include. No custom implementation required.
#include <ext/pb_ds/assoc_container.hpp> #include <ext/pb_ds/tree_policy.hpp> using namespace __gnu_pbds; typedef tree<int, null_type, less<int>, rb_tree_tag, tree_order_statistics_node_update> ordered_set; ordered_set os; os.insert(3); os.insert(1); os.insert(4); os.find_by_order(0); // iterator to smallest: 1 os.order_of_key(3); // 1 (count of elements strictly less than 3)
Available on GCC and on most LeetCode environments. See Order-Statistics Tree for the full theory.
Five Things to Know Before Your C++ Coding Interview
If you are going in with C++, these patterns need to come from muscle memory, not from searching your notes mid-interview.
- Min-heap syntax:
priority_queue<int, vector<int>, greater<int>> - Custom comparator struct for priority queues with pairs
- Safe erase-while-iterating on vector: capture the return value of
erase() find()vsoperator[]on map for reads that should not insertlower_bound/upper_boundon bothmapand sortedvector
If any of these requires a lookup, drill that one specifically. Partial STL knowledge is worse than none: you write code that compiles and silently does the wrong thing, then spend the interview debugging the library instead of the algorithm.
SpaceComplexity runs full mock interviews in C++ with rubric-based scoring across algorithm correctness, code quality, and communication, so you can find out which traps catch you before they catch you in front of a hiring panel.
Further Reading
- cppreference: STL Containers, authoritative reference for every container's API and complexity guarantees
- GeeksforGeeks: C++ STL Cheat Sheet, condensed syntax for common containers
- cppreference: Iterator Invalidation Rules, complete table of when each container invalidates iterators
- cppreference: std::priority_queue, template parameters and comparator requirements
- ISO C++ Core Guidelines, idiomatic C++ patterns endorsed by the language designers