DSA
The NeetCode 150 in C++
Practise the NeetCode 150 list in C++ with compilable C++17 examples, STL idioms, complexity notes and thita practice for interview preparation. Start now.

Practise the NeetCode 150 by pattern
Working through the NeetCode 150 in C++ is not just a matter of translating an algorithm into syntax. C++ gives you a powerful standard library, but the details matter: choosing unordered_map or map, remembering that priority_queue is a max-heap, avoiding iterator invalidation and selecting a wide enough integer type can decide whether a correct idea becomes a correct submission.
This guide explains how to work through the NeetCode 150 list, practised on thita, specifically in modern C++17. The examples use problems available on thita and are deliberately interview-sized rather than complete tutorials for every algorithm. Thita is not affiliated with NeetCode.
How should you work through the NeetCode 150 in C++?
Use the NeetCode 150 by pattern as your checklist, then treat each problem as an opportunity to learn a reusable pattern and a C++ implementation habit. The list contains 150 problems: 28 Easy, 101 Medium and 21 Hard. Every problem on the list that is live on thita runs in the browser in C++ against the problem's test cases; the NeetCode 150 sheet shows which ones are live.
A productive sequence is:
- Read the problem and identify the pattern before writing syntax.
- State the brute-force idea, then explain the optimisation.
- Choose the C++ container that expresses the invariant clearly.
- Write the solution with explicit types and boundary checks.
- Test edge cases, then state time and space complexity aloud.
- Revisit the same pattern later without looking at the previous code.
The NeetCode 150 sheet groups the list using thita's DSA patterns and retains NeetCode's topic as a tag. That distinction is useful: a heap describes a tool, while a top-K pattern describes the shape of the problem you need to recognise.
Do not turn the list into a memorisation exercise. In a C++ coding interview, explain why a lookup is expected constant time, why sorting is necessary, or why an iterator remains valid. The implementation is evidence of your reasoning, not a substitute for it.
Which C++ STL choices matter in an interview?
Should you use unordered_map or map?
Use unordered_map when you need key lookup, insertion or counting and do not need keys in sorted order. Its lookup, insertion and deletion are average constant time, although a pathological hash distribution can make an operation linear. It is a natural first choice for frequency tables, complements and visited-state maps.
Use map when you need keys ordered, need to iterate from smallest to largest, or want logarithmic worst-case operations. Ordering can remove a separate sort, support range-oriented reasoning, or make the result deterministic. Do not choose it merely because it sounds more general: a tree-based map usually has more per-entry overhead than a hash table.
Here is the complement lookup pattern from Two Sum. The input vector is read through a const reference, and a structured binding makes the pair returned from find easier to read.
CPP
Time: O(n) average and O(n²) in the worst case if hashing degenerates. Space: O(n).
The storedValue binding is not needed by the method, but it shows the shape of a map entry: a key and a value. In production interview code, an unused binding should usually be removed for clarity. Structured bindings are especially useful with map entries, queue pairs and tuples. Remember that a binding to a map entry gives you a const key: map keys must not be modified in place.
For an ordering-sensitive variation, Group Anagrams can use map so the groups are emitted according to their sorted signature. An unordered_map is also valid when output ordering is unrestricted.
CPP
Time: O(n · m log m + n · m log n), where n is the number of strings and m is the maximum string length. The second term accounts for ordered map comparisons. Space: O(n · m), including the returned groups and stored signatures.
Why is priority_queue a common C++ trap?
priority_queue<T> is a max-heap by default: top() is the largest element. For a min-heap, supply greater<> as the comparator:
priority_queue<int, vector<int>, greater<>> minHeap;
The comparator describes which element has priority, not a conventional less-than sorting function. For pairs, greater<pair<int, int>> produces lexicographic min-heap ordering. For a more specific rule, write a named comparator or a lambda and check its tie behaviour explicitly.
K Closest Points to Origin is a useful example of a bounded max-heap. The heap keeps the currently selected points, with the farthest selected point at the top so it can be removed when a closer point arrives.
CPP
Time: O(n log k). Space: O(k), excluding the returned vector.
The squared distance uses long long before multiplication. That matters even when the input elements themselves are int: multiplication happens in the operand type unless you widen an operand first. Similar care is needed for products, sums, areas, path costs and binary-search totals.
What should you know about lower_bound and upper_bound?
On a sorted range, lower_bound returns the first position whose value is not less than the target. upper_bound returns the first position whose value is greater than the target. Both return iterators, so compare the result with end() before dereferencing it.
The library versions are usually preferable to handwritten loops because they make the boundary contract explicit. On a vector, random-access iterators give logarithmic overall behaviour. More generally, the algorithms can work with forward iterators, but advancing those iterators need not be logarithmic. For a sorted vector, the difference between the returned iterator and begin() is the index.
Binary Search can be written directly with lower_bound:
CPP
Time: O(log n). Space: O(1).
For duplicate data, the pair lower_bound and upper_bound gives a half-open range of equal values: [lower_bound(begin, end, x), upper_bound(begin, end, x)). Its length is the frequency of x. A frequent bug is calling these functions on an unsorted range, or dereferencing end() when the target is absent.
Which C++ coding interview idioms prevent wrong answers?
When should a function take a vector by const reference?
A parameter written as const vector<int>& nums avoids copying the entire vector and promises not to modify it. This is the normal signature for read-only input. Use a non-const reference only when mutation is part of the intended interface; use pass-by-value when you deliberately want a local copy to modify.
The same principle applies to strings and nested vectors. In range-based loops, prefer const string& word when you only inspect an element. Writing for (auto word : words) copies each string, which can be an avoidable cost and can hide the fact that the algorithm is read-only.
Why must arithmetic be widened deliberately?
long long is not a decoration. Write 1LL * a * b, cast before adding large values, and use a long long accumulator when the result can exceed the range of int. Converting only after an overflowing int multiplication is too late.
Product of Array Except Self demonstrates prefix and suffix products while keeping the output as the working storage. It also handles empty input, repeated values and zeroes explicitly. The widened accumulator follows the problem's integer-result contract while avoiding unnecessary int overflow during intermediate multiplication.
CPP
Time: O(n). Space: O(1) auxiliary space, excluding the returned vector.
The exact return type must match the platform's contract. Here it remains vector<int>, while the intermediate products use long long. If a different problem permits results beyond int, choose a return type that matches that problem instead of casting blindly.
What becomes invalid after an erase?
Erasing from a vector invalidates the erased iterator and iterators after it, because later elements move. Erasing from a list invalidates only the erased element's iterator; other list iterators remain valid. For unordered associative containers, erasing an element invalidates that element's iterator, while a rehash caused by insertion can invalidate iterators more broadly.
The classic C++ design is an LRU cache: a list stores recency order and an unordered map stores iterators into that list. On access, erase the old list node before inserting the refreshed node. Never use the erased iterator afterwards.
CPP
Time: O(1) average per operation. Space: O(capacity).
In a loop over an associative container, the safe pattern is to assign the return value of erase to the iterator when the container supports that overload: it = container.erase(it). Incrementing an iterator after erasing it is undefined behaviour.
How should you build strings?
For a small number of concatenations, result += piece is clear and efficient enough. When you know or can estimate the final size, call reserve first. This reduces reallocations while preserving the simple append interface. reserve is an allocation hint, not a size change: it does not create accessible elements. Use resize when you need to write by index.
Encode and Decode Strings is a good exercise because the delimiter must be unambiguous. A length prefix lets the decoder consume exactly the next string, including strings containing punctuation or the delimiter itself.
CPP
Time: O(total characters). Space: O(total characters) for the returned data, excluding the encoded input.
The encoder handles an empty vector by returning an empty string, and the decoder handles that string by returning an empty vector. The length prefix means duplicate strings remain duplicate strings instead of being confused with a set. Also remember that string::size() returns an unsigned type; avoid mixing it carelessly with negative signed indices.
Which NeetCode topics are easier or harder in C++?
Arrays, hashing, two pointers, sliding windows, binary search and heaps are often comfortable in C++ once the core containers are familiar. vector gives contiguous storage and random access, algorithms such as sort and lower_bound are concise, and unordered_map handles frequency-based patterns with little code.
Linked lists, trees and graphs are algorithmically language-neutral, but C++ adds ownership and lifetime questions. Be clear about whether a helper node is borrowed from the judge or allocated by your method, and do not delete memory that the platform still owns. Recursive tree code is compact, but stack depth and reference types still deserve attention.
Backtracking and dynamic programming are usually straightforward in C++ when state is represented by references and small value types. They become harder when accidental copying occurs: passing a path or grid by value in a recursive function can dominate the intended complexity. Use const references for read-only state and mutate-and-undo deliberately for a shared path.
Tries, advanced graphs and design problems tend to expose more C++ detail. You may need a custom comparator, a node structure, a queue of pairs, a disjoint-set class or careful iterator management. These are not reasons to avoid the topics; they are reasons to practise the implementation until the data structure no longer consumes your interview attention.
Which C++ STL components should you know cold?
Know the behaviour, not just the names:
vector: contiguous storage,push_back,reserve, and invalidation after reallocation or erasure.string:+=,find,substr,reserve, and the difference between size and capacity.unordered_mapandunordered_set: average constant-time lookup, hashing, and the fact that iteration is unordered.mapandset: sorted keys, logarithmic operations, and stable ordering.stack,queueanddeque: the interfaces behind parenthesis, BFS and sliding-window patterns.priority_queue: default max-heap, min-heap syntax and comparator direction.sort,lower_bound,upper_bound,reverseandaccumulate: the algorithms that replace routine loops.pair,tupleand structured bindings: convenient representation of state and map entries.list: constant-time splicing and erasure when you already have an iterator, useful for LRU designs.
Also know what the STL does not guarantee. Hash-table operations are not automatically worst-case constant time, operator[] on a map-like container can insert a missing key, and a reference or iterator can dangle after its container changes.
How can you practise the NeetCode 150 in C++ on thita?
Start at the NeetCode 150 by pattern, choose a problem, and run your C++ code against that problem's test cases in the browser. Use the result diagnostically: a wrong answer usually points to a boundary invariant, while a compilation error often reveals a missing include, an incorrect comparator or a type mismatch.
After solving, explain the approach without looking at the editor. Say which container you chose, what it stores, why the loop is correct and how the complexity follows. Then change one detail: add duplicates, use negative values, test an empty input, or ask what happens when a heap contains ties.
Finally, rehearse the communication side in an AI mock interview. It asks follow-up questions and runs your code, so you can practise writing C++ while also defending your design. That is the difference between having a solution saved somewhere and being ready to produce and explain it in a C++ coding interview.
The goal is not to memorise 150 disconnected answers. Build fluency with patterns, C++ STL for interviews and the small language decisions that keep a good algorithm correct.
Topics
Related on Thita
Your next step
Practise the NeetCode 150 by pattern
Track the NeetCode 150 list on the DSA patterns sheet, run every live problem in 6 languages against real test cases, then rehearse it out loud in an AI mock interview.