Algorithms
Complexity, sorting, searching, string and graph algorithms, STL algorithms, and common data structures.
101 questions
JuniorCodeVery commonCheck whether a binary tree is balanced
Check whether a binary tree is balanced
Heights of left and right subtrees differ by at most 1 at every node. The optimal O(n) DFS returns the subtree height, or −1 as a sentinel for 'unbalanced', so height computation and balance check happen in one pass.
Open full question →Common mistakes
- ✗Using the O(n²) approach with a separate height function called at every node
- ✗Only checking the root's children instead of every node recursively
- ✗Not returning a sentinel value — returning just bool loses the height information needed by the parent
Follow-up questions
- →What is an AVL tree and how does it maintain balance on insertions?
- →What is the difference between height-balanced and weight-balanced trees?
JuniorCodeVery commonImplement binary search on a sorted array
Implement binary search on a sorted array
Binary search works on a sorted range by repeatedly halving the search space. Maintain low and high bounds; compare the middle element with the target; move the bound toward the target side. Time O(log n), space O(1).
Open full question →Common mistakes
- ✗Integer overflow in
mid = (low + high) / 2when low and high are large — uselow + (high - low) / 2 - ✗Off-by-one in loop condition:
while (low < high)vswhile (low <= high)changes semantics - ✗Not ensuring the input array is sorted — binary search on unsorted data gives wrong results
Follow-up questions
- →How does
std::lower_bounddiffer fromstd::binary_search? - →How would you extend binary search to find the leftmost / rightmost occurrence of a value?
JuniorCodeVery commonRecursive value search in a binary search tree
Recursive value search in a binary search tree
In a BST a node's left subtree holds smaller values and the right subtree larger values. Recursively: empty → nullptr; equal → return; target less → go left; otherwise go right. Time O(h): O(log n) when balanced, O(n) worst case.
Open full question →Common mistakes
- ✗Not checking for nullptr before accessing node->val
- ✗Using a general tree search (visit every node) instead of BST property to prune branches
- ✗Forgetting that BST guarantees are on values, not structural balance
Follow-up questions
- →How do you insert and delete a node in a BST?
- →What is an AVL tree or a Red-Black tree and why do they guarantee O(log n)?
JuniorTheoryVery commonWhat is algorithm complexity (Big-O)?
What is algorithm complexity (Big-O)?
Big-O describes how worst-case running time or memory grows with input size n, ignoring constant factors. Common classes: O(1), O(log n), O(n), O(n log n), O(n²), O(2ⁿ); the same notation applies to space complexity.
Common mistakes
- ✗Confusing worst-case (O), average-case (Θ), and best-case (Ω) — interviews usually expect worst-case
- ✗Ignoring constant factors that matter in practice: O(n) with a large constant can be slower than O(n²) for small n
- ✗Forgetting space complexity — recursive algorithms often trade time for stack space
Follow-up questions
- →What is amortised complexity? Give the example of
std::vector::push_back. - →Why is O(n log n) considered optimal for comparison-based sorting?
JuniorCodeVery commonCount the number of set bits in an integer
Count the number of set bits in an integer
Use Kernighan's trick: n &= n - 1 clears the lowest set bit, repeat until n is 0 — runs in O(k) where k is the number of set bits. In C++20 prefer std::popcount, which compiles to a single instruction.
Common mistakes
- ✗Using a naive bit-by-bit loop (O(32)) instead of Kernighan's trick (O(set bits))
- ✗Not handling negative numbers correctly when the argument is a signed int — use unsigned
- ✗Forgetting about
std::popcountin C++20 which makes this a one-liner
Follow-up questions
- →How would you check if a number is a power of two using bit manipulation?
- →What does
n & (n-1)do in general?
JuniorCodeVery commonCompute Fibonacci numbers (iterative, recursive, memoised)
Compute Fibonacci numbers (iterative, recursive, memoised)
Naive recursion is O(2ⁿ) — catastrophically slow. Bottom-up iteration uses two variables and runs in O(n) time and O(1) space. Top-down memoisation (cache of results) is also O(n) but O(n) space. Matrix exponentiation achieves O(log n).
Open full question →Common mistakes
- ✗Writing naive recursion without memoisation — fib(40) already takes seconds
- ✗Overflow for large n — use
uint64_tor__int128explicitly and note that fib(93) is the last value fitting uint64_t - ✗Off-by-one in the base case: fib(0)=0, fib(1)=1, fib(2)=1
Follow-up questions
- →How does matrix exponentiation compute Fibonacci in O(log n)?
- →How would you compute fib(n) mod M for very large n?
JuniorCodeVery commonFind the unique element in an array where all others appear twice
Find the unique element in an array where all others appear twice
XOR all elements together. Pairs cancel out (a XOR a = 0), so the result is the single element that has no pair. Time O(n), space O(1), single pass.
Open full question →Common mistakes
- ✗Using a hash map — O(n) space is unnecessary when XOR gives O(1)
- ✗Sorting the array — O(n log n) and modifies input
- ✗Not generalising: this only works when exactly one element appears an odd number of times
Follow-up questions
- →How would you find two unique elements when all others appear twice?
- →How would you find the unique element when all others appear three times?
JuniorCodeVery commonInsert an element into a singly-linked list
Insert an element into a singly-linked list
Insertion at head is O(1): create a new node, set its next to the current head, update the head. Insertion at position k is O(k): traverse to the predecessor, splice in the new node. Insertion at tail without a tail pointer is O(n).
Open full question →Common mistakes
- ✗Forgetting to handle insertion at position 0 (head) separately
- ✗Walking one node too far — you need the node before the insertion point, not at it
- ✗Not updating the head pointer when inserting at position 0
Follow-up questions
- →How would you insert in sorted order into an already-sorted list?
- →What is the complexity of building a sorted linked list by repeated sorted insertion?
JuniorCodeVery commonLength of the longest substring without repeating characters
Length of the longest substring without repeating characters
Slide a window with a left pointer and a map of each character's last index. For each right, if the character was seen at or after left, jump left to one past that index. The current window length is right - left + 1; track its maximum. One pass, O(n) time.
Common mistakes
- ✗Moving
lefttolastSeen+1even whenlastSeenis before the current window, shrinking it wrongly - ✗Resetting the window from scratch on a repeat, degrading to O(n²)
- ✗Confusing the count of distinct characters with the longest duplicate-free run
Follow-up questions
- →Why must
leftonly move forward, never backward? - →How would you return the substring itself, not just its length?
JuniorCodeVery commonCheck whether a string is a palindrome
Check whether a string is a palindrome
Use two pointers starting at both ends, advancing toward the center. Compare characters; if they differ the string is not a palindrome. Time O(n), space O(1).
Open full question →Common mistakes
- ✗Reversing the whole string and comparing — allocates O(n) extra memory unnecessarily
- ✗Not handling case sensitivity or non-letter characters for real-world inputs
- ✗Using signed index arithmetic that can underflow when the string is empty
Follow-up questions
- →How would you check if a linked list is a palindrome?
- →What is the longest palindromic substring problem and what algorithm solves it efficiently?
JuniorCodeVery commonReverse a singly-linked list
Reverse a singly-linked list
Use three pointers: prev (initially nullptr), curr, and next. Iterate: save next, point curr->next to prev, advance prev to curr, advance curr to saved next. When curr is null, prev is the new head. Time O(n), space O(1).
Open full question →Common mistakes
- ✗Losing the next node before overwriting curr->next — always save
next = curr->nextfirst - ✗Returning curr instead of prev at the end — curr is null when the loop exits
- ✗Not handling empty list or single-element list edge cases
Follow-up questions
- →How would you reverse a doubly-linked list?
- →How would you reverse only a sub-range [m, n] of a linked list?
JuniorCodeVery commonImplement string reversal
Implement string reversal
Use two indices (or iterators) from both ends and swap characters toward the centre. O(n) time, O(1) space in-place. std::reverse from <algorithm> does this in one line.
Common mistakes
- ✗Returning a new reversed string when in-place was requested (wastes O(n) memory)
- ✗Not handling empty string or single-character string edge cases
- ✗Naive reversal on UTF-8 strings — must work with codepoints, not bytes
Follow-up questions
- →How would you reverse words in a sentence without reversing the characters in each word?
- →How do you reverse a UTF-8 encoded string correctly?
JuniorCodeVery commonImplement a sorting algorithm
Implement a sorting algorithm
Quicksort: average O(n log n), in-place, not stable, degrades to O(n²) on bad pivots — use median-of-three or random pivot. Merge sort: guaranteed O(n log n), stable, requires O(n) extra space.
Open full question →Common mistakes
- ✗Always choosing the first element as pivot — O(n²) on sorted input
- ✗Not handling the base case (array of size 0 or 1)
- ✗Confusing merge sort stability with quicksort's in-place property
Follow-up questions
- →What is introsort and why does
std::sortuse it instead of pure quicksort? - →When would you choose merge sort over quicksort?
JuniorTheoryVery commonWhat are sorting algorithms and which do you know?
What are sorting algorithms and which do you know?
Sorting algorithms rearrange a collection in order. Key ones: bubble/selection/insertion sort O(n²), merge sort O(n log n) stable, quicksort O(n log n) avg / O(n²) worst, heap sort O(n log n). std::sort uses introsort — a quicksort + heapsort + insertion-sort hybrid — for guaranteed O(n log n).
Common mistakes
- ✗Choosing bubble sort for any real task — it is only pedagogically useful
- ✗Not knowing that quicksort degrades to O(n²) on already-sorted input without a good pivot strategy
- ✗Forgetting that stable sort preserves relative order of equal elements — matters when sorting by secondary key
Follow-up questions
- →What is the difference between
std::sortandstd::stable_sort? - →When would you use a radix sort or counting sort instead of a comparison sort?
JuniorTheoryVery commonExplain the stack and queue data structures.
Explain the stack and queue data structures.
A stack is a LIFO (last-in, first-out) structure: push adds to the top, pop removes from the top. A queue is FIFO (first-in, first-out): enqueue adds to the back, dequeue removes from the front. In C++ the standard library provides std::stack and std::queue as container adaptors, both typically backed by std::deque.
Common mistakes
- ✗Confusing stack overflow with the stack data structure — stack overflow is a runtime condition on the call stack
- ✗Using
std::stackorstd::queuewhen you need to iterate — they have no iterators; usestd::dequeorstd::listdirectly - ✗Forgetting that
std::queue::front()andback()are O(1) butstd::stackonly exposestop()
Follow-up questions
- →How would you implement a stack that also supports
min()in O(1)? - →When would you use a circular buffer instead of a deque-backed queue?
JuniorCodeVery commonCheck whether a binary tree is symmetric
Check whether a binary tree is symmetric
Symmetry is a mirror property of the two subtrees, not of a single node. Recurse comparing left against right: two nodes mirror iff their values match and left.l mirrors right.r and left.r mirrors right.l. Both null is symmetric; one null is not. An empty tree is symmetric. O(n) time.
Common mistakes
- ✗Comparing a node's own two children instead of mirroring across the two subtrees
- ✗Treating one-null/one-present as a match instead of a failure
- ✗Relying on a traversal-palindrome, which passes for non-symmetric trees
Follow-up questions
- →Why is comparing a node's own left and right children insufficient?
- →How would you write this iteratively with a queue?
JuniorTheoryVery commonWhat are pre-order, in-order, post-order, and level-order traversals and when to use each?
What are pre-order, in-order, post-order, and level-order traversals and when to use each?
Pre-order (root, left, right): copy or serialise a tree. In-order (left, root, right): yields sorted output on a BST. Post-order (left, right, root): delete a tree or evaluate an expression tree. Level-order (BFS): queue-based, prints by levels.
Common mistakes
- ✗Using in-order on a non-BST and expecting sorted output
- ✗Implementing post-order iteratively without two stacks (or visited flag)
- ✗Confusing pre-order and DFS by depth — pre-order is a DFS
Follow-up questions
- →How would you serialise and deserialise a binary tree?
- →What is Morris traversal and when is it useful?
MiddleTheoryVery commonWhen would you use BFS vs DFS for graph traversal?
When would you use BFS vs DFS for graph traversal?
BFS (queue) explores level-by-level — best for shortest path in unweighted graphs. DFS (stack/recursion) goes deep along each branch — natural for topological sort, cycle detection and SCCs. Both are O(V+E); BFS uses O(V) memory, DFS uses O(h).
Common mistakes
- ✗Using DFS for shortest unweighted path — finds a path, not the shortest
- ✗Forgetting to mark visited and looping forever in cyclic graphs
- ✗Recursing too deep on DFS — stack overflow
Follow-up questions
- →How would you find the shortest path in a weighted graph (Dijkstra)?
- →When does iterative DFS beat recursive DFS in practice?
MiddleTheoryVery commonWhat is Big-O notation and how do you determine complexity?
What is Big-O notation and how do you determine complexity?
Big-O describes the asymptotic upper bound of growth. Count dominant operations in terms of n, drop lower-order terms and constants; multiply for nested loops, add for independent ones; for recursion use the Master Theorem.
Common mistakes
- ✗Forgetting that O-big only describes growth rate, not actual speed — O(n) can be slower than O(n²) for small n
- ✗Not recognising O(log n) patterns: binary search, BST operations, balanced tree height
- ✗Treating hash map operations as always O(1) — worst case is O(n) due to collisions
Follow-up questions
- →Explain the Master Theorem and when it applies to recurrences.
- →What is the difference between O, Ω (Omega), and Θ (Theta) notations?
MiddleCodeVery commonDetect a cycle in a singly-linked list (Floyd's algorithm)
Detect a cycle in a singly-linked list (Floyd's algorithm)
Floyd's tortoise and hare uses two pointers from the head moving at different speeds: slow advances by 1, fast by 2; if they ever meet inside the list a cycle exists, and if fast reaches nullptr the list is acyclic. Time O(n), space O(1).
Open full question →Common mistakes
- ✗Using a hash set to track visited nodes — O(n) space, not needed with Floyd's
- ✗Not checking
fast && fast->nextbefore advancing — leads to null dereference - ✗Stopping on fast == nullptr but not checking fast->next == nullptr (needed for even-length lists)
Follow-up questions
- →How do you find the start of the cycle after detecting it?
- →How do you find the length of the cycle?
MiddleCodeVery commonTwo Sum: indices of two numbers summing to a target
Two Sum: indices of two numbers summing to a target
Use a hash map from value to index. For each element check whether target - nums[i] is already in the map; if so, return both indices. Otherwise insert nums[i] → i. One pass, O(n) time and O(n) space — versus the O(n²) brute-force double loop.
Common mistakes
- ✗Returning the values themselves instead of their indices
- ✗Using the same element twice to form the pair
- ✗Settling for the O(n²) double loop when O(n) is expected
Follow-up questions
- →How would you adapt this if the input array were already sorted?
- →What changes if there can be multiple valid pairs, or none at all?
JuniorCodeCommonImplement int atoi(const char* str)
Implement int atoi(const char* str)
Skip leading whitespace, handle optional sign, then accumulate digits: result = result * 10 + digit. Handle overflow (clamp to INT_MIN/INT_MAX per the standard) and stop at the first non-digit character.
Common mistakes
- ✗Not handling leading whitespace (the standard
atoiskips it) - ✗Integer overflow during accumulation — check before multiplying
- ✗Not handling the negative sign correctly: '-' before digits sets a flag
Follow-up questions
- →What is the difference between
atoi,strtol,std::stoi, andstd::from_chars? - →How does
std::from_charsdiffer fromstd::stoiin terms of error handling?
JuniorCodeCommonCount the left leaves of a binary tree
Count the left leaves of a binary tree
The left-leaf property cannot be decided from a node alone — it depends on the parent. Recurse passing an isLeft flag: a node counts when it is a leaf (no children) and isLeft is true. Recurse into left with isLeft = true and into right with isLeft = false. The root is passed isLeft = false. O(n) time.
Common mistakes
- ✗Counting all leaves regardless of whether they are left children
- ✗Trying to decide 'leftness' from the node itself instead of the parent context
- ✗Counting a single-node tree's root as a left leaf
Follow-up questions
- →Why can't a node decide on its own that it is a left leaf?
- →How would you instead sum the values of the left leaves?
JuniorCodeCommonEquilibrium index where left sum equals right sum
Equilibrium index where left sum equals right sum
First compute the total sum. Then sweep once keeping a running left sum; at index i the right sum is total - left - a[i]. When left == total - left - a[i], return i. One pass after the total, O(n) time and O(1) extra space. Return -1 if none matches.
Common mistakes
- ✗Recomputing the right sum from scratch at every index, making it O(n²)
- ✗Forgetting that
a[i]itself belongs to neither side, mis-deriving the right sum - ✗Not handling the empty array or returning a wrong sentinel when no index balances
Follow-up questions
- →Why does subtracting
a[i]from the total give exactly the right-side sum? - →How would you find all equilibrium indices instead of the first one?
JuniorCodeCommonIndex of the first non-repeating character
Index of the first non-repeating character
First pass: count the occurrences of each character in a hash table (or fixed array for a known alphabet). Second pass: walk the string left to right and return the index of the first character whose count is 1. Return -1 if none. O(n) time.
Common mistakes
- ✗Rescanning the string per character, degrading to O(n²)
- ✗Returning the first count-1 entry from an unordered map, losing original order
- ✗Confusing 'first not seen yet' with 'occurs exactly once'
Follow-up questions
- →Why must the second pass go over the string, not over the map?
- →How would you do it in one pass if you also stored each character's index?
JuniorCodeCommonFizzBuzz variant: Foo for /2, Bar for /3, Buzz for /6
FizzBuzz variant: Foo for /2, Bar for /3, Buzz for /6
Loop i from 1 to n, build an empty string, append Foo when i % 2 == 0, Bar when i % 3 == 0, Buzz when i % 6 == 0. If the string is still empty print i, otherwise print the string. Because the rules append, a multiple of 6 yields FooBarBuzz.
Common mistakes
- ✗Using else-if so a value gets only one label instead of appending all that apply
- ✗Not clarifying whether a multiple of 6 prints FooBarBuzz or only Buzz
- ✗Forgetting to print the number when no rule matched
Follow-up questions
- →How would you rewrite it without an explicit loop, using a functional map?
- →Why is the
% 6rule redundant if% 2and% 3already append?
JuniorTheoryCommonWhat graph algorithms do you know?
What graph algorithms do you know?
Core graph algorithms: BFS (shortest path in unweighted graphs, O(V+E)), DFS (cycle detection, topological sort, O(V+E)), Dijkstra (shortest path with non-negative weights, O((V+E) log V)), Bellman-Ford (handles negative edges, O(VE)), Floyd-Warshall (all pairs, O(V³)), Kruskal/Prim for MST.
Common mistakes
- ✗Using Dijkstra on graphs with negative edge weights — it produces incorrect results; use Bellman-Ford
- ✗Forgetting to track visited nodes in BFS/DFS leading to infinite loops on cyclic graphs
- ✗Confusing topological sort with BFS — topological sort uses DFS or Kahn's BFS-based algorithm, and only applies to DAGs
Follow-up questions
- →How do you detect a cycle in a directed graph? An undirected graph?
- →What is the difference between a tree, a DAG, and a general graph?
JuniorCodeCommonDetermine if a year is a leap year
Determine if a year is a leap year
A year is a leap year if: divisible by 4, EXCEPT centuries (divisible by 100), UNLESS also divisible by 400. So 2000 is a leap year, 1900 is not, 2024 is. Implement with the standard three-condition check.
Open full question →Common mistakes
- ✗Checking only
year % 4 == 0and forgetting the century exception - ✗Writing nested ifs that make the logic hard to read instead of one boolean expression
- ✗Not handling year 0 or negative years if the API accepts them
Follow-up questions
- →What is the Gregorian calendar rule and why was it introduced?
- →How does
std::chrono::year::is_leap()work in C++20?
JuniorCodeCommonRemove entries with even values from unordered_map and print their keys
Remove entries with even values from unordered_map and print their keys
You cannot erase from a container while iterating with a range-for loop — this invalidates the iterator. Safe approaches: collect keys to erase, then erase in a separate pass; or use the C++20 std::erase_if helper which handles this cleanly.
Common mistakes
- ✗Erasing inside a range-for — undefined behaviour because the iterator is invalidated
- ✗Using
map.erase(it)without capturing the returned next iterator in a manual loop - ✗Not knowing about
std::erase_if(C++20) which is the cleanest solution
Follow-up questions
- →What is iterator invalidation and which containers are most/least prone to it?
- →How does
erasereturn the next valid iterator in most containers?
JuniorCodeCommonPer-character maximum consecutive repetition count
Per-character maximum consecutive repetition count
Walk the string tracking the current character and its run length. When the character changes, update that character's recorded maximum in a map if the just-ended run is longer, then start a new run of length 1. Flush the last run after the loop. One pass, O(n) time.
Open full question →Common mistakes
- ✗Reporting total frequency instead of the longest consecutive run
- ✗Forgetting to flush the final run after the loop ends
- ✗Folding case when the spec is case-sensitive (or vice versa)
Follow-up questions
- →Why does sorting destroy the answer for an interleaved character like
aba? - →How would you keep the output keys in first-appearance order instead of sorted?
JuniorDebuggingCommonFix the longest-run-of-ones counter that drops the final run
Fix the longest-run-of-ones counter that drops the final run
best is updated only in the else (zero) branch, so a trailing run of 1s that never hits a zero is never compared. Fix it by updating best = max(best, cur) on every 1 (inside the if), or once more after the loop. O(n).
Common mistakes
- ✗Believing the counter is correct because it works for arrays ending in 0
- ✗Updating max only at run boundaries marked by a zero
- ✗Adding a post-loop check but forgetting the empty-array case
Follow-up questions
- →How would the fix change if you also had to return the run's start index?
- →What is the analogous bug for the longest run of any fixed value?
JuniorCodeCommonMinimum product of any pair of array elements
Minimum product of any pair of array elements
Track the two smallest and two largest values in one pass. The minimum product is the smaller of min1*min2 (both small/negative) and max1*max2 (two large negatives give a small product), but the overall minimum is min1 * max1 when signs are mixed — compare all relevant candidates. O(n) time, O(1) space; watch for overflow with long long.
Common mistakes
- ✗Considering only the two smallest, missing the two-large-negatives case
- ✗Overflowing
intwhen multiplying two large-magnitude values - ✗Sorting (O(n log n)) when an O(n) tracking pass is expected
Follow-up questions
- →Why can two large negative numbers never be the minimum product?
- →How does the answer change for the maximum product instead?
JuniorCodeCommonNormalize a Unix file path (handle ., .., and //)
Normalize a Unix file path (handle ., .., and //)
Split the path on /. Push each component onto a stack; skip empty components (from //) and .; on .. pop the stack if it is non-empty (above-root is ignored). Finally join the stack with /, prefixed by a leading /. One pass, O(n) time.
Common mistakes
- ✗Popping the stack on
..when it is already empty (climbing above root) - ✗Forgetting that consecutive slashes produce empty components to skip
- ✗Character-level edits that mishandle a
..whose parent is multi-character
Follow-up questions
- →Why split into components rather than editing the raw character string?
- →How would relative paths (no leading slash) change the
..-above-root rule?
JuniorCodeCommonCollapse runs of spaces to a single space in place
Collapse runs of spaces to a single space in place
Use a read and a write index. Copy each character to the write position, but write a space only when the previously written character was not a space. After the pass, resize the string to the write index. One pass, O(n) time, O(1) extra space; leading and trailing single spaces are preserved.
Open full question →Common mistakes
- ✗Trimming leading or trailing spaces when the spec says only collapse runs
- ✗Forgetting to resize the string, leaving stale characters at the tail
- ✗Calling
eraseper extra space, turning an O(n) job into O(n²)
Follow-up questions
- →How does the single trailing space survive when the input ends with several?
- →What changes if you also had to trim the ends?
JuniorCodeCommonOne-edit-apart check between two strings
One-edit-apart check between two strings
If the lengths differ by more than 1, return false. If equal, count character mismatches and accept at most one. If they differ by 1, scan with two pointers allowing exactly one skip in the longer string; any second mismatch fails. One pass, O(n) time, O(1) space.
Common mistakes
- ✗Treating the equal-length and differ-by-one cases identically, mishandling the insert/delete skip
- ✗Forgetting to reject immediately when lengths differ by more than one
- ✗Allowing a second mismatch after the single permitted edit
Follow-up questions
- →Why does a length difference greater than one let you return early?
- →How does the two-pointer skip model an insertion versus a deletion?
JuniorCodeCommonRemove zeros from a vector preserving order in O(n)
Remove zeros from a vector preserving order in O(n)
Use a write index. Walk the vector with a read index; for each non-zero element, copy it to the write position and advance the write index. After the pass, resize the vector to the write index. One pass, O(n) time, O(1) extra space. The idiomatic form is the erase-remove idiom: v.erase(std::remove(v.begin(), v.end(), 0), v.end()).
Common mistakes
- ✗Calling
eraseper zero, shifting the tail each time and degrading to O(n²) - ✗Swap-with-last, which removes zeros but scrambles the order of the kept elements
- ✗Forgetting to resize/erase the leftover tail after compacting
Follow-up questions
- →What does
std::removeactually do to the tail, and why iserasestill needed? - →How would you instead move all zeros to the end, keeping non-zero order?
JuniorCodeCommonReverse the characters of each word, keeping word order
Reverse the characters of each word, keeping word order
Walk the string; at the start of each maximal non-space run, find its end, then reverse that run in place with two pointers swapping inward. Spaces are skipped and left untouched, so all spacing is preserved. One pass, O(n) time, O(1) extra space.
Open full question →Common mistakes
- ✗Normalizing or collapsing spaces when the spec requires preserving them exactly
- ✗Reversing across spaces and merging adjacent words
- ✗Allocating a new buffer when an in-place O(1)-space solution is expected
Follow-up questions
- →How do you preserve double spaces while reversing only the word characters?
- →How would you also reverse the order of the words?
JuniorCodeCommonReverse word order while keeping every space in place
Reverse word order while keeping every space in place
Extract the word tokens in order. Then walk the original string: copy spaces unchanged, and for each maximal non-space run pour in the next word taken from the END of the token list. The space gaps stay fixed; only the words are reversed. O(n).
Open full question →Common mistakes
- ✗Normalizing spaces (the classic reverse-words) instead of keeping the exact pattern
- ✗Mishandling leading or trailing spaces
- ✗Breaking on a string of only spaces or with no spaces at all
Follow-up questions
- →Why does the classic split-and-join approach fail this variant?
- →How would you do it in place to avoid extra allocation?
JuniorCodeCommonRun-length encode an A-Z string, omitting count for singletons
Run-length encode an A-Z string, omitting count for singletons
Walk the string once tracking the current character and its run length. When the next character differs, emit the character, append the count only if the run exceeds one, then reset. After the loop, flush the last group. Validate each character is A-Z and throw otherwise. O(n) time.
Common mistakes
- ✗Forgetting to flush the last run after the loop ends
- ✗Appending a
1for single characters instead of leaving them bare - ✗Emitting only one digit of a multi-digit count, or skipping input validation
Follow-up questions
- →How does the decoder distinguish the count digits from letters when decompressing?
- →What would change if a single character could legitimately be a digit?
JuniorCodeCommonFind elements present in only one of two arrays (symmetric difference) using STL
Find elements present in only one of two arrays (symmetric difference) using STL
Sort both arrays, then use std::set_symmetric_difference to get elements that appear in exactly one of the two arrays. Alternatively, insert one array into an unordered_set and check the other — O(n+m) average with O(n) extra space.
Common mistakes
- ✗Forgetting that
std::set_symmetric_differencerequires sorted input - ✗Not using
std::back_inserteras the output iterator - ✗Confusing symmetric difference (in exactly one) with difference (in first but not second)
Follow-up questions
- →What is the time complexity of
std::set_symmetric_difference? - →How would you find the intersection of two arrays using STL?
JuniorCodeCommonShortest distance between an X and a Y in a string
Shortest distance between an X and a Y in a string
Sweep once keeping the last seen index of X and of Y. On an X, if a Y was seen, update the minimum with i - lastY; on a Y, symmetrically with i - lastX. If either letter never appears, return 0. One pass, O(n) time, O(1) space.
Common mistakes
- ✗Updating only
lastXand notlastY(or vice versa), missing pairs in one direction - ✗Returning a stale large sentinel when one of the letters never appears instead of 0
- ✗Computing distance only from the first occurrence rather than the nearest one
Follow-up questions
- →Why is tracking just the last seen index of each letter enough?
- →How would you extend this to the shortest distance between any two of K letters?
JuniorCodeCommonSorted squares of a sorted array in O(n)
Sorted squares of a sorted array in O(n)
Use two pointers at both ends: the largest square is at one of the ends because the input is sorted. Compare abs(nums[left]) and abs(nums[right]), write the larger square into the output from the back, and move that pointer inward. One pass, O(n) time and O(n) space.
Common mistakes
- ✗Squaring then sorting, losing the O(n) bound the two-pointer approach gives
- ✗Assuming the squares are already sorted because the input was — false when negatives are present
- ✗Filling the output front-to-back instead of back-to-front, so the larger squares land in the wrong slots
Follow-up questions
- →Where exactly does the largest square live before you start, and why?
- →How would the approach change if the input were not sorted at all?
JuniorTheoryCommonWhich STL algorithms have you used? What is the advantage over hand-written loops?
Which STL algorithms have you used? What is the advantage over hand-written loops?
Common: std::sort, find/find_if, transform, for_each, accumulate, copy, remove_if, unique, lower_bound/upper_bound, count_if. Wins over hand-written loops: intent is in the name, the implementation is tested and optimised, and many support parallel execution policies since C++17.
Common mistakes
- ✗Using
std::remove/std::remove_ifwithout the subsequenterasecall — the erase-remove idiom is required to actually shrink the container - ✗Passing non-sorted ranges to
std::binary_search,std::lower_bound, orstd::upper_bound - ✗Calling
std::sorton astd::list— lists have no random-access iterators; uselist::sort()member instead
Follow-up questions
- →How does
std::transform_reducecombine transformation and reduction efficiently? - →What are the C++20 ranges algorithms and how do they differ from classic STL algorithms?
JuniorTheoryCommonWhat are the components of the STL?
What are the components of the STL?
The STL has four main components: containers (sequence, associative, unordered, adapters), iterators (the glue between containers and algorithms), algorithms (sort, find, transform, accumulate, etc.), and function objects/lambdas that customise algorithm behaviour.
Common mistakes
- ✗Thinking STL and the C++ standard library are the same — the standard library is a superset including
<iostream>,<thread>, etc. - ✗Using raw loops where an STL algorithm would be clearer and potentially more optimised
- ✗Forgetting that allocators are the fifth component, relevant for custom memory strategies
Follow-up questions
- →What are range adaptors in C++20 (
std::ranges)? - →Why should you prefer
std::begin()/std::end()over.begin()/.end()?
JuniorTheoryCommonWhat string algorithms do you know?
What string algorithms do you know?
Key string algorithms: naive substring search O(n·m), KMP (Knuth-Morris-Pratt) O(n+m), Boyer-Moore O(n/m) average, Rabin-Karp (rolling hash) for multiple pattern search, and Z-algorithm for prefix matching. For edit distance: Levenshtein (dynamic programming O(n·m)).
Common mistakes
- ✗Using
std::string::findin a loop resulting in O(n²) when KMP would give O(n) - ✗Forgetting that string comparison in C++ is O(n) — not O(1) like pointer comparison
- ✗Ignoring locale/encoding issues when processing non-ASCII strings
Follow-up questions
- →How does the Z-algorithm differ from KMP? When would you prefer one over the other?
- →What search strategy improvements did C++17 add to
std::search?
JuniorCodeCommonReturn the two largest numbers in an array in one pass
Return the two largest numbers in an array in one pass
Keep two variables, max1 and max2, both initialised to the smallest possible value (not 0). For each element: if it exceeds max1, shift max1 into max2 and update max1; else if it exceeds max2, update max2. The crucial else if keeps the old max from being lost. One pass, O(n).
Common mistakes
- ✗Omitting the
else if (x > max2)branch, so the second-largest is overwritten by the largest - ✗Initialising the maxima to 0, which breaks for all-negative arrays
- ✗Not handling an array of fewer than two elements
Follow-up questions
- →Why does initialising to 0 fail for an all-negative array?
- →How would you generalise this to the K largest elements?
JuniorCodeCommonURLify: replace spaces with %20 in place
URLify: replace spaces with %20 in place
Two passes. First count the spaces to compute the final length. Then write from the back: copy each character to its final slot, and for each space write '0', '2', '%' (reverse order). Writing right-to-left means you never overwrite unprocessed input. O(n) time, O(1) extra space.
Common mistakes
- ✗Writing front-to-back and overwriting characters not yet processed
- ✗Forgetting to use the pre-sized capacity and writing past the original length
- ✗Emitting the
%20characters in the wrong order during the reverse pass
Follow-up questions
- →Why does writing from the back avoid clobbering unread characters?
- →How would the solution differ if you could not modify the buffer in place?
JuniorCodeCommonImplement a minimal vector<T> with push_back, push_front, pop_back, pop_front, size, clear
Implement a minimal vector<T> with push_back, push_front, pop_back, pop_front, size, clear
A vector owns a heap-allocated array, a size (used elements), and a capacity (allocated slots). push_back amortises O(1) by doubling capacity when full. push_front is O(n) as it must shift all elements. Proper copy/move semantics and destructor are required for correctness (Rule of Five).
Common mistakes
- ✗Using
new T[n]which default-initialises all elements — prefer raw memory + placement new for efficiency - ✗Forgetting to call destructors on existing elements before
clear()for non-trivial T - ✗Growing by 1 instead of doubling — leads to O(n²) total push_back cost
Follow-up questions
- →Why does
std::vectoruse capacity doubling and not tripling or fixed increment? - →How would you implement
insertat an arbitrary position efficiently?
JuniorCodeCommonCount words in a sentence
Count words in a sentence
Scan the string tracking whether the previous character was whitespace. Each transition from whitespace to non-whitespace increments the word count. Handle multiple consecutive spaces and leading/trailing spaces correctly.
Open full question →Common mistakes
- ✗Counting spaces instead of transitions from space to non-space — fails on multiple spaces
- ✗Off-by-one: not counting the last word when string doesn't end with a space
- ✗Not handling the empty string case
Follow-up questions
- →How would you count unique words in a sentence?
- →How do you tokenise a string by a custom delimiter?
MiddleCodeCommonDispense an ATM amount with the fewest bills; when does greedy fail?
Dispense an ATM amount with the fewest bills; when does greedy fail?
Walk denominations from largest to smallest, taking min(amount / denom, stock[denom]) of each. Greedy is optimal only because the denominations are mutually divisible. Compute the plan into a temporary and apply it only if the remainder is zero — so a failure leaves stock untouched.
Common mistakes
- ✗Mutating the real stock before knowing the amount is fully assembled
- ✗Assuming greedy is optimal for arbitrary denominations, not just divisible ones
- ✗Leaving zero-count denominations in the dispensed result
Follow-up questions
- →Give a denomination set where greedy fails but a solution exists.
- →How would you switch to a DP solution for arbitrary denominations?
MiddleCodeCommonCount islands of land in a 0/1 grid via flood fill
Count islands of land in a 0/1 grid via flood fill
Scan every cell. On an unvisited land cell, increment the island count and flood-fill its whole connected component with DFS or BFS, marking each reached land cell as visited (e.g. set it to 0). The fill stops at water and borders; each cell is visited a constant number of times.
Open full question →Common mistakes
- ✗Counting each land cell as an island instead of each connected component
- ✗Not marking visited cells, so the same island is counted repeatedly
- ✗Mixing up 4- vs 8-directional adjacency, changing the answer
Follow-up questions
- →How would 8-directional connectivity change the count?
- →How do you avoid stack overflow on a huge grid with recursive DFS?
MiddleCodeCommonCount substrings with no repeating characters in O(n)
Count substrings with no repeating characters in O(n)
Sliding window: keep left as the start of the current repeat-free window and lastSeen[c] as each character's last index. For each right end j, set left = max(left, lastSeen[c]+1), then add j - left + 1 (the valid left ends) to the total. One pass, O(n).
Common mistakes
- ✗Moving
lefttolastSeen[c]instead oflastSeen[c]+1, leaving the repeat inside the window - ✗Failing to clamp with
max(left, ...)soleftjumps backward on an old repeat - ✗Using
intfor the total when n is large enough to overflow
Follow-up questions
- →How does this differ from finding the length of the longest such substring?
- →Why must
leftonly ever move forward?
MiddleTheoryCommonHow does Dijkstra's algorithm work and what data structure makes it efficient?
How does Dijkstra's algorithm work and what data structure makes it efficient?
Dijkstra finds shortest paths from a source on graphs with non-negative weights. Pop the minimum-distance vertex from a priority queue, relax its neighbours, push updates. Binary heap — O((V+E) log V); negative weights need Bellman-Ford.
Common mistakes
- ✗Using Dijkstra with negative edges and getting wrong results
- ✗Not handling 'stale' entries in the priority queue (vertex with outdated distance)
- ✗Forgetting to mark a vertex as finalised after popping the smallest distance
Follow-up questions
- →When does A* outperform Dijkstra?
- →What's the complexity of Dijkstra with a Fibonacci heap and is it ever practical?
MiddleTheoryCommonWhat is dynamic programming and what's the difference between memoisation and tabulation?
What is dynamic programming and what's the difference between memoisation and tabulation?
DP exploits overlapping subproblems and optimal substructure by caching subproblem solutions. Memoisation (top-down) is recursion with on-demand caching; tabulation (bottom-up) iteratively fills a table from base cases and often allows space reduction.
Common mistakes
- ✗Implementing memoisation with
std::mapwhen an array would do — O(log n) lookup vs O(1) - ✗Forgetting to handle the base case correctly in tabulation
- ✗Not reducing space when the recurrence only needs the last row/column
Follow-up questions
- →How would you reconstruct the optimal solution after computing the DP table?
- →Compare top-down and bottom-up for the knapsack problem.
MiddleCodeCommonGroup an array of strings into sets of anagrams
Group an array of strings into sets of anagrams
Give every word a canonical key shared by all its anagrams, then bucket words by key in a hash map. The key is either the word's sorted characters or a 26-entry character-count signature. Words with the same key are anagrams. Collect the map's values as the groups.
Open full question →Common mistakes
- ✗Sorting the array and hoping anagrams become adjacent, which they do not
- ✗Grouping by length or first letter, which collides non-anagrams
- ✗Falling back to O(n^2) pairwise permutation checks
Follow-up questions
- →Why is a 26-count signature a faster key than sorting each word?
- →How would you handle Unicode words where 26 buckets are not enough?
MiddleCodeCommonImplement a queue with a fixed-size circular buffer
Implement a queue with a fixed-size circular buffer
A circular buffer queue uses a fixed array with head and tail indices wrapping around modulo capacity. enqueue writes to tail and advances it; dequeue reads from head and advances it. Full condition: (tail + 1) % cap == head. This gives O(1) enqueue/dequeue with no dynamic allocation.
Common mistakes
- ✗Confusing full and empty conditions — use (tail + 1) % cap == head for full, head == tail for empty
- ✗Wasting one slot to distinguish full from empty — alternative: use a separate size counter
- ✗Off-by-one in the modulo arithmetic
Follow-up questions
- →How would you make this queue thread-safe for one producer and one consumer?
- →What is the advantage of a circular buffer over a linked-list-backed queue?
MiddleTheoryCommonCompare quicksort and merge sort by complexity, stability, and memory.
Compare quicksort and merge sort by complexity, stability, and memory.
Quicksort: average O(n log n), worst O(n²) on bad pivots, in-place (O(log n) stack), not stable; usually fastest in practice. Merge sort: guaranteed O(n log n), O(n) extra memory, stable. Use quicksort (introsort) by default; pick merge sort when stability is required or for external sorting.
Common mistakes
- ✗Implementing quicksort with first/last as pivot and hitting O(n²) on sorted input
- ✗Choosing merge sort for in-memory sort 'because guaranteed' — usually slower than introsort
- ✗Implementing recursion without iterative fallback for deep stacks
Follow-up questions
- →What is introsort and how does it avoid worst-case quicksort?
- →How does external merge sort handle data larger than RAM?
MiddleCodeCommonElements of one sorted list not present in another
Elements of one sorted list not present in another
Two-pointer merge: when a[i] < b[j], a[i] is absent from b, so emit it and advance i. When a[i] == b[j], skip a[i] (it is present). When a[i] > b[j], advance j. After b is exhausted, emit the rest of a. O(n + m), O(1).
Common mistakes
- ✗Mishandling duplicates, e.g. dropping both copies of a value present once in b
- ✗Forgetting to emit the tail of a once b is exhausted
- ✗Failing to advance a pointer on equality, causing an infinite loop
Follow-up questions
- →How does duplicate handling change if b can contain repeated values?
- →Why is the two-pointer merge preferable to a hash set when both inputs are already sorted?
MiddleCodeCommonFind a contiguous subarray summing to X (with negatives)
Find a contiguous subarray summing to X (with negatives)
Walk the array accumulating a prefix sum P, with a hash map from prefix value to earliest index seeded 0 → -1. At each j, if P - X is in the map, the subarray after that index up to j sums to X. Negatives rule out a window, so the map gives O(n).
Common mistakes
- ✗Using a sliding window despite negative numbers, which breaks the monotonic-sum assumption
- ✗Forgetting to seed
0 → -1so a subarray starting at index 0 is missed - ✗Letting the prefix sum overflow
inton large inputs
Follow-up questions
- →How would the approach simplify if all numbers were guaranteed non-negative?
- →Why is the
0 → -1seed entry essential?
MiddleCodeCommonFind a root-to-leaf path in a binary tree with a given sum
Find a root-to-leaf path in a binary tree with a given sum
Run a DFS carrying the remaining sum and the current path. At each node subtract its value; at a leaf, accept the path iff the remaining sum is now zero. Recurse into children, pushing the node before and popping after (backtracking). Return the first accepted path. O(n) time.
Open full question →Common mistakes
- ✗Accepting at an internal node instead of requiring a leaf endpoint
- ✗Assuming all values are positive and pruning negative branches
- ✗Forgetting to pop the node on backtracking, leaking it into the wrong path
Follow-up questions
- →How does allowing negative values rule out the early-stop pruning?
- →How would you return all such paths instead of the first one?
JuniorCodeOccasionalLength of the longest run of identical characters
Length of the longest run of identical characters
Sweep once with a current run length. While the next character equals the current one, extend the run; on a change, reset to 1. Track the maximum run length across the pass. One pass, O(n) time, O(1) space; the empty string yields 0.
Open full question →Common mistakes
- ✗Resetting the run length to 0 instead of 1 on a character change
- ✗Forgetting to compare the final run against the maximum after the loop
- ✗Confusing total character frequency with the longest contiguous run
Follow-up questions
- →How would you extend this to the longest substring with at most K distinct characters?
- →Why is total frequency not the same as the longest run?
MiddleCodeOccasionalCheck whether all integer points are collinear
Check whether all integer points are collinear
Fix the first two points as a reference direction (dx, dy). A point p is on that line iff the cross product dx*(p.y-y0) - dy*(p.x-x0) is zero. Check it for every point. Use long long for the products to avoid overflow; no division means vertical lines work too. O(n).
Common mistakes
- ✗Using floating-point slope, losing precision or dividing by zero on a vertical line
- ✗Computing the cross product in
int, overflowing on large coordinates - ✗Testing only a subset of points instead of every point against the reference line
Follow-up questions
- →Why is the cross product preferred over comparing slopes?
- →What edge cases arise with fewer than three points?
MiddleCodeOccasionalCount index pairs whose value difference is at least K
Count index pairs whose value difference is at least K
Sort the array. For each i, binary-search the first index whose value is at least a[i]+K; every element from there to the end forms a valid pair, so add their count. When K = 0 this counts all i <= j pairs. Total O(n log n) — far better than the O(n²) double loop.
Common mistakes
- ✗Forgetting that K=0 includes the self-pair (i, i) in the count
- ✗Counting ordered pairs when the spec asks for i <= j (or vice versa)
- ✗Using the original (unsorted) indices and missing that sorting is allowed since only values matter
Follow-up questions
- →How would counting pairs with difference at most K (instead of at least) change the approach?
- →Why is sorting safe even though the question mentions indices?
MiddleCodeOccasionalReturn all meetings that overlap at least one other meeting
Return all meetings that overlap at least one other meeting
Sort by from, then sweep tracking the running maximum to of all earlier-starting meetings. A meeting overlaps an earlier one when its from < maxEnd; when so, mark both it and the meeting that set maxEnd. Use a flag per meeting so each is reported once. O(n log n).
Common mistakes
- ✗Comparing only adjacent sorted intervals, missing a meeting overlapped by an earlier non-adjacent one
- ✗Mixing closed and half-open boundary tests, so touching intervals are wrongly counted as overlapping
- ✗Reporting a meeting twice when it overlaps several others
Follow-up questions
- →How does the half-open
[from, to)rule change the boundary comparison? - →How does this differ from finding the maximum number of overlapping meetings?
MiddleCodeOccasionalFind a unique element in a container in one pass
Find a unique element in a container in one pass
For integers where each duplicate appears exactly twice, XOR all values: a^a=0 leaves only the unique one — O(n) time, O(1) space. In the general case, build an unordered_map<T,int> of counts in one pass, then return the entry with count==1.
Common mistakes
- ✗Claiming 'one pass' when the second scan of the map/set is actually a second pass
- ✗Using a sorted approach — requires two passes or sorting (O(n log n))
- ✗Not clarifying the problem constraints before choosing the algorithm
Follow-up questions
- →How would you find the first non-repeating character in a string in one pass?
- →What if elements can appear any number of times and you need the one with odd count?
MiddleCodeOccasionalImplement fuzzysearch: is needle a subsequence of haystack?
Implement fuzzysearch: is needle a subsequence of haystack?
Use two pointers. Walk haystack; whenever the current haystack char equals the current needle char, advance the needle pointer. The needle is a subsequence iff its pointer reaches the end. One linear pass over the haystack, O(|haystack|), constant extra space.
Common mistakes
- ✗Confusing subsequence (order preserved, gaps allowed) with substring (contiguous)
- ✗Only checking character presence and ignoring relative order
- ✗Advancing the needle pointer on every haystack char instead of only on a match
Follow-up questions
- →What clarifying questions matter here (empty needle, case sensitivity)?
- →Why does the single-pass greedy match never miss a valid subsequence?
MiddleCodeOccasionalDetect cycles and unreachable states in a directed graph
Detect cycles and unreachable states in a directed graph
Use DFS with three-colour marking: white (unvisited), grey (in current path), black (fully processed). A back edge (grey→grey) indicates a cycle. Unreachable nodes remain white after a full DFS from all source nodes. A deadlock state is a cycle in a dependency/wait-for graph.
Open full question →Common mistakes
- ✗Using only a visited set — it detects visited nodes but not back edges (on-path vs already-completed)
- ✗Not performing DFS from all unvisited nodes — misses disconnected components
- ✗Confusing undirected-graph cycle detection (union-find) with directed-graph cycle detection (DFS colours)
Follow-up questions
- →How does topological sort relate to cycle detection in a DAG?
- →What is Tarjan's algorithm for finding strongly connected components?
MiddleCodeOccasionalAdd two hexadecimal numbers given as strings
Add two hexadecimal numbers given as strings
Walk both strings from the last character toward the first, converting each hex digit to 0–15, summing with a carry. Store sum % 16 as the next output digit and keep sum / 16 as carry. After both ends, emit any remaining carry, then reverse the built string. O(max length).
Common mistakes
- ✗Forgetting to emit the final carry, dropping the leading digit of
ff + ff - ✗Padding on the wrong side and misaligning the place values
- ✗Mishandling the a–f digit to value conversion (off by the 10 offset)
Follow-up questions
- →How would you generalize this to an arbitrary base?
- →Why is processing from the least-significant end necessary for the carry?
MiddleCodeOccasionalK elements closest in value to a[index] in a sorted array
K elements closest in value to a[index] in a sorted array
Start two pointers at index-1 and index+1, taking a[index] itself first. Each step compare the left and right candidates' distance to a[index] and take the closer side, until k elements are collected. Guard both ends. O(k) since the array is sorted.
Common mistakes
- ✗Running off the left or right end without a bounds check
- ✗Mishandling the single-element array where neither pointer is valid
- ✗Picking the farther of two equally-distant candidates when a tie-break is specified
Follow-up questions
- →How does this differ from finding the k closest to an arbitrary value x, not a[index]?
- →How would you make the tie-break deterministic toward smaller values?
MiddleCodeOccasionalK elements closest to a value x in a sorted array
K elements closest to a value x in a sorted array
Binary-search the position of x, then grow a size-k window. Each step compare the boundaries' distance to x; if x - arr[left-1] <= arr[right] - x move left, else right. The <= favours the smaller value on a tie. The window stays sorted. O(log n + k).
Common mistakes
- ✗Returning the k elements only to the right of x instead of the closest on both sides
- ✗Using
<instead of<=and breaking the smaller-value tie-break - ✗Letting the left/right pointers run past the array bounds
Follow-up questions
- →How does this differ from finding k closest to a[index] rather than a free value x?
- →Why does the window remain contiguous throughout?
MiddleCodeOccasionalMultiply a big decimal number (digit string) by a single digit
Multiply a big decimal number (digit string) by a single digit
Walk from the least-significant digit. At each position compute product = digit * n + carry, store product % 10 back, set carry = product / 10. After the loop, while carry > 0 append carry % 10 as new high digits. Little-endian storage lets the carry flow forward naturally.
Common mistakes
- ✗Forgetting to append the leftover carry after the last digit
- ✗Falling back to a fixed-width integer, which overflows for long numbers
- ✗Iterating most-significant-first so the carry has nowhere to flow
Follow-up questions
- →How does the algorithm change if digits are 32-bit limbs instead of base 10?
- →Why does least-significant-first storage simplify carry propagation?
MiddleCodeOccasionalLongest strictly monotone contiguous subarray in O(n)
Longest strictly monotone contiguous subarray in O(n)
Scan once, tracking the current run's start and direction. When the next pair flips direction, start a new run at the previous index; when it is equal, start at the current index. Keep the longest run seen. Equality always breaks a strict run. O(n) time, O(1) space.
Open full question →Common mistakes
- ✗Treating equal adjacent values as extending a strictly monotone run
- ✗Resetting the run start to the current index instead of the previous one on a direction flip
- ✗Off-by-one when the longest run ends at the last element
Follow-up questions
- →Why must a new run start at the previous index, not the current one?
- →How would the logic change for non-strict (allowing equal) monotonicity?
MiddleCodeOccasionalMinimum absolute difference between elements of two arrays
Minimum absolute difference between elements of two arrays
Sort both arrays, then walk them with two pointers. At each step record abs(a[i] - b[j]) and advance the pointer at the smaller value — moving the larger one could only widen the gap. The minimum over this merge is the global minimum. Subtract in 64-bit to avoid overflow. O(n log n).
Common mistakes
- ✗Advancing the wrong pointer, so closer pairs are skipped
- ✗Subtracting in 32-bit ints and overflowing on extreme values
- ✗Not handling an empty array, which has no valid pair
Follow-up questions
- →Why does advancing the smaller value never miss the optimum?
- →How would
lower_boundgive an alternative O(n log n) without merging?
MiddleCodeOccasionalSplit an array into 3 parts minimizing total first-element cost
Split an array into 3 parts minimizing total first-element cost
The first part always starts at index 0, so its cost is fixed as nums[0]. The other two parts start at any two indices > 0, so their costs are the two smallest values among nums[1..]. The answer is nums[0] plus those two minima, in one O(n) pass.
Common mistakes
- ✗Sorting and missing that the first part's cost is forced to be nums[0]
- ✗Including nums[0] when searching for the two minima of the other parts
- ✗Using an O(n²) double loop when one pass suffices
Follow-up questions
- →Why is the first part's cost forced and the rest free to be any two later indices?
- →How would the answer change for splitting into k parts?
MiddleCodeOccasionalShortest substring containing every letter of a given alphabet
Shortest substring containing every letter of a given alphabet
Use a sliding window with a count of required characters still missing, kept in a hash map. Expand right, decrementing the count when a needed char is first covered. While the window covers all required chars, record it if shorter and shrink from left. Run to the end so a window ending at the last char counts.
Common mistakes
- ✗Expanding the window but never shrinking from the left to minimise it
- ✗Stopping early and missing a window that ends at the last character
- ✗Not reporting failure when the alphabet is never fully covered
Follow-up questions
- →How does the missing-count let you check coverage in O(1) per step?
- →How does the answer change if extra characters are not allowed?
MiddleCodeOccasionalDot product of two run-length-encoded vectors
Dot product of two run-length-encoded vectors
Walk both lists with two pointers, tracking the remaining count of the current run on each side. At each step consume min(remaining) positions, adding value_l * value_r * min to the accumulator, then advance whichever run is exhausted. O(|l|+|r|), no expansion, 64-bit sum.
Common mistakes
- ✗Assuming the two RLE run boundaries line up, instead of consuming the min of remaining counts
- ✗Expanding the vectors and losing the O(|l|+|r|) advantage
- ✗Overflowing a 32-bit accumulator when value*count products are large
Follow-up questions
- →How does this differ from adding two sparse vectors, which merges instead of multiplying?
- →Why is consuming the min of the two remaining counts the key step?
MiddleCodeOccasionalCommon elements in every K-prefix of two arrays, in O(N)
Common elements in every K-prefix of two arrays, in O(N)
Advance both prefixes in lockstep with two hash sets seenA, seenB and a running common. When a new value from a is already in seenB, increment common; same for a new b value in seenA. Add it to its set. Record common after each step. One pass, O(N).
Common mistakes
- ✗Rebuilding the prefix sets per K instead of extending them incrementally
- ✗Counting a duplicate value as a new intersection match more than once
- ✗Forgetting that the new element must be checked against the OTHER array's set
Follow-up questions
- →How does the answer change if intersection must respect multiplicity (min of counts)?
- →Why does the running counter stay correct when both prefixes grow together?
MiddleCodeOccasionalShorten an L/R/U/D path by cutting out closed sub-loops
Shorten an L/R/U/D path by cutting out closed sub-loops
Walk the moves tracking the current (x, y) coordinate and a hash map of each visited coordinate to its output-path position. When a coordinate repeats, the moves since its first visit form a closed loop: truncate the output back to that position and drop the coordinates added in between.
Common mistakes
- ✗Reducing to net displacement, which discards the realised route shape
- ✗Only cancelling adjacent reversals, missing larger loops like R,D,L,U
- ✗Forgetting to seed the start coordinate at output index 0
Follow-up questions
- →Why does net displacement give the wrong answer for
[D,R,U]? - →How do you erase the in-between coordinates from the map when truncating?
MiddleCodeOccasionalStream numbers across linked files, printing a running average, no cycles
Stream numbers across linked files, printing a running average, no cycles
Treat files as graph nodes and references as edges; traverse with DFS keeping a visited set of file paths so a cycle never reopens a file. Keep a running sum and count; for each numeric line add it and print sum / count. A reference follows the named file only if unvisited.
Common mistakes
- ✗Re-summing all numbers per line instead of keeping a running sum and count
- ✗Omitting the visited set, so a reference cycle loops forever
- ✗Tracking visits by timestamp or size rather than the file path
Follow-up questions
- →Why does a visited set turn this into a standard graph traversal?
- →How would you keep the running average numerically stable for many values?
MiddleCodeOccasionalWrap a near-sorted int stream into a fully sorted stream
Wrap a near-sorted int stream into a fully sorted stream
Keep a min-heap of size at most k + 1. On each get, refill it from the source until it holds k + 1 items or the source ends, then pop the smallest. Every value is within k of its slot, so the smallest of any k + 1-window is the next in order. Return -1 when it drains.
Common mistakes
- ✗Buffering the whole stream, defeating the bounded-memory point
- ✗Sizing the window at
kinstead ofk + 1, so the next element can still be smaller - ✗Not draining the buffer after the source ends, dropping the tail
Follow-up questions
- →Why must the buffer hold
k + 1rather thankelements? - →What data structure gives O(log k) pop-min and insert here?
MiddleCodeOccasionalSolve a Sudoku puzzle
Solve a Sudoku puzzle
Use backtracking: find the first empty cell, try digits 1–9, check row/column/box constraints, recurse. If no digit works, backtrack. Time complexity is bounded by O(9^m) where m is the number of empty cells, but constraint propagation pruning makes it fast in practice.
Open full question →Common mistakes
- ✗Checking the full board at every step instead of only the affected row, column, and box
- ✗Not returning
truewhen a solution is found — the recursion must propagate success upward - ✗Using 1-indexed coordinates causing off-by-one errors in box calculation
Follow-up questions
- →How does constraint propagation (arc consistency) improve backtracking performance?
- →What is the 'most constrained variable' heuristic for choosing which cell to fill next?
MiddleCodeOccasionalCollapse a list of integers into a range string
Collapse a list of integers into a range string
Sort the array. Walk it tracking the start of the current consecutive run; when the next value is not prev + 1, flush the run as start or start-end and begin a new one. Flush the last run after the loop. O(n log n) for the sort, O(n) to build.
Common mistakes
- ✗Forgetting to sort, so non-adjacent consecutive values are missed
- ✗Printing a singleton as
k-kinstead of justk - ✗Dropping the final range because it is flushed only inside the loop
Follow-up questions
- →How would duplicates change the logic if they were allowed?
- →Why is flushing after the loop necessary?
MiddleCodeOccasionalCan a string become a palindrome by removing exactly one char?
Can a string become a palindrome by removing exactly one char?
Two pointers from both ends. On the first mismatch, try skipping either the left or the right character and check whether the remaining span is a palindrome. If the string is already a palindrome, removing a central char keeps it one, so return true. O(n), O(1).
Open full question →Common mistakes
- ✗Treating the spec as 'at most one' removal when it says exactly one
- ✗Checking only one side at the mismatch instead of trying both removals
- ✗Re-scanning the whole string for each candidate removal, making it O(n²)
Follow-up questions
- →How does 'exactly one' differ subtly from 'at most one' for an already-palindromic string?
- →Why is trying both sides at the first mismatch sufficient?
MiddleCodeOccasionalFind the vertical axis of symmetry of a set of 2D points in O(n)
Find the vertical axis of symmetry of a set of 2D points in O(n)
The only candidate axis is (minX + maxX) / 2, so the doubled axis is minX + maxX. Put every point in a hash set; for each point (x, y) its mirror is (minX + maxX - x, y). If every mirror is present the axis is valid. Use the doubled value to avoid fractions. O(n).
Common mistakes
- ✗Comparing real-valued axes with floats instead of doubling to stay integer
- ✗Assuming the centroid is the axis when the distribution is uneven
- ✗Overflowing on
minX + maxXfor large coordinates
Follow-up questions
- →Why is
minX + maxXthe only possible doubled axis? - →How do duplicate points affect the mirror check?
MiddleCodeOccasionalPick a server at random in proportion to configured load weights
Pick a server at random in proportion to configured load weights
Build the cumulative distribution: walk the weights accumulating a running sum and return the first index where the sum exceeds r. This maps the uniform draw onto buckets sized by weight, so each server's chance equals its weight. O(k), or O(log k) with a prefix-sum array and binary search.
Common mistakes
- ✗Comparing
ragainst each raw weight instead of the cumulative sum - ✗Off-by-one at the boundary, e.g. using
<=so the last bucket is unreachable - ✗Assuming uniform selection already respects the weights
Follow-up questions
- →How would you speed repeated draws with a prefix-sum array and binary search?
- →What changes if the weights do not sum to exactly 1?
SeniorCodeOccasionalFind a substring that is a permutation of S in O(|T|)
Find a substring that is a permutation of S in O(|T|)
Slide a window of length |S| over T and keep one counter of how many character-counts still mismatch the pattern. On each slide add the entering char and drop the leaving char, updating that counter in O(1). When it hits zero the window is an anagram. O(|T|), alphabet-independent.
Common mistakes
- ✗Re-scanning the whole frequency array per window, making the constant depend on alphabet size
- ✗Forgetting to both add the entering and remove the leaving character on each slide
- ✗Confusing anagram (same multiset) with equality (same order)
Follow-up questions
- →How would you return all anagram start indices instead of the first?
- →Why does maintaining a single mismatch counter make the per-slide work O(1)?
SeniorCodeOccasionalExpand a bracket grammar (term)[N] into the resulting string
Expand a bracket grammar (term)[N] into the resulting string
Keep a stack of partial strings. On ( push the current string and start a fresh one; on ) parse the bracketed N, pop the saved string and append the just-built string repeated N times; otherwise append the letter. Multi-digit N parses digit by digit, N == 0 appends nothing.
Common mistakes
- ✗Parsing only the first digit of a multi-digit count like
[28] - ✗Mishandling
N == 0, leaving stale characters instead of an empty term - ✗Trying a single accumulator, which breaks on nested brackets
Follow-up questions
- →How does this differ from the LeetCode
N[term]decode-string syntax? - →Could you expand lazily to avoid materialising a huge output string?
SeniorCodeOccasionalLowest common ancestor in a tree with parent pointers, O(1) space
Lowest common ancestor in a tree with parent pointers, O(1) space
Compute each node's depth by walking up via parent to the root. Advance the deeper node upward by the depth difference so both are level, then walk both up in lockstep until the pointers meet — that node is the LCA. O(h) time, O(1) space, no extra structures.
Common mistakes
- ✗Using a hash set of ancestors, violating the O(1) space requirement
- ✗Assuming a binary search tree and comparing values, which fails on a general tree
- ✗Forgetting to equalize depths before walking up in lockstep
Follow-up questions
- →How do you compute the depths without extra storage?
- →What is the O(d) variant using exponential upward steps?
SeniorCodeOccasionalStrip smileys :-))) and :-((( from a message in one pass
Strip smileys :-))) and :-((( from a message in one pass
Scan once as a small state machine. At each position check for : then - then a run of ) or (; if found, skip the whole token and continue past it, else copy the current character out. No backtracking into emitted text, so nesting is not collapsed. O(n).
Common mistakes
- ✗Backtracking and collapsing nested smileys that the spec says to leave alone
- ✗Forgetting that the smiley needs a colon, a dash, AND a non-empty bracket run
- ✗Doing repeated erase passes, turning a one-pass problem into O(n²)
Follow-up questions
- →Why does avoiding backtracking give the no-nesting behaviour the spec wants?
- →How would you do it truly in place with a write index?
SeniorCodeOccasionalAdd two sparse vectors given as sorted (index, value) pairs
Add two sparse vectors given as sorted (index, value) pairs
Two-pointer merge of the sorted lists: at equal indices add the values, otherwise emit the smaller index and advance that pointer. Skip any result whose summed value is zero (it is no longer sparse-nonzero). Linear in the combined length, O(|l|+|r|).
Open full question →Common mistakes
- ✗Forgetting to drop entries whose summed value is zero
- ✗Advancing both pointers when only one index matched
- ✗Accumulating into a narrow type and overflowing when two large values add
Follow-up questions
- →How does this differ from a sparse-vector dot product, which multiplies instead?
- →Why must the zero-sum result be removed to stay properly sparse?
JuniorTheoryRareWhat does it mean for a sort to be stable, and which STL sorts are stable?
What does it mean for a sort to be stable, and which STL sorts are stable?
Stable sort preserves the relative order of equal-key elements. std::stable_sort is stable (merge-sort, O(n log n) with O(n) extra memory); std::sort is not stable (introsort, O(n log n) average, in-place). For multi-criteria sorting, apply stable sorts from the least to the most significant key.
Common mistakes
- ✗Using
std::sortand being surprised by jumbled order of equal-key elements - ✗Sorting by all criteria at once with a complex comparator instead of cascading stable sorts
- ✗Forgetting that stable_sort needs O(n) extra memory
Follow-up questions
- →How would you sort by (department asc, salary desc) using stable sorts?
- →What is timsort and where is it used (Python, Java)?
MiddleTheoryRareWhat do C++20 ranges and views add over the classic STL algorithms?
What do C++20 ranges and views add over the classic STL algorithms?
Ranges accept a single range instead of iterator pairs (std::ranges::sort(v)), reducing iterator mismatch bugs. Views (v | filter | transform) compose lazy transformations; compile times rise.
Common mistakes
- ✗Storing a view of a temporary container — dangling
- ✗Expecting views to be reusable without resetting — most are single-pass
- ✗Underestimating compile-time impact in template-heavy projects
Follow-up questions
- →How does
std::ranges::to(C++23) materialise a view back into a container? - →What is a sentinel and how does it generalise iterators?
SeniorDesignRareRequests arrive in time order. Two operations interleave: a user generates an event, and a query asks how many users generated at least 1000 events in the last 5 minutes. Design a data structure handling both in amortized O(1) per request, with the constant independent of the 1000 threshold and the 5-minute window width. Describe the structures, why each operation is amortized O(1), and the memory pitfalls (the window emptying, and stale per-user entries never being cleaned up).
Requests arrive in time order. Two operations interleave: a user generates an event, and a query asks how many users generated at least 1000 events in the last 5 minutes. Design a data structure handling both in amortized O(1) per request, with the constant independent of the 1000 threshold and the 5-minute window width. Describe the structures, why each operation is amortized O(1), and the memory pitfalls (the window emptying, and stale per-user entries never being cleaned up).
Keep a queue of windowed events, a map userId → count, and a running robotCount of users at or above the threshold. On each op evict events older than the window, add the new one, and adjust robotCount on a threshold crossing. Each event is enqueued and dequeued once, so amortized O(1).
Common mistakes
- ✗Not handling the window emptying out at some moment
- ✗Never deleting users whose count drops to zero, leaking memory on a long stream
- ✗Letting the constant depend on the 1000 threshold or the 5-minute width
Follow-up questions
- →How do you keep memory bounded when only events (no queries) arrive for a long time?
- →Why does the running robotCount avoid rescanning all users per query?
SeniorCodeRareFind two subtrees with the same set of letters in O(N)
Find two subtrees with the same set of letters in O(N)
Post-order recursion: a node's letter set is (1 << (Value-'A')) OR-ed with the masks of its children. Store each computed mask in a hash map from mask to node; the first time a mask repeats you have two equivalent subtrees. One traversal, O(N) time and O(N) space.
Common mistakes
- ✗Counting letter frequencies instead of treating the subtree as a set (the spec ignores frequencies)
- ✗Recomputing a subtree's mask from scratch at each node instead of OR-ing children's masks
- ✗Forgetting to seed the current node's own letter into its mask
Follow-up questions
- →How would you instead return the equivalent pair with the largest combined subtree size?
- →Why is a 32-bit integer enough, and when would you need a different descriptor?
SeniorTheoryRareWhat is execution policy for parallel STL algorithms?
What is execution policy for parallel STL algorithms?
C++17 added execution policies to many STL algorithms: std::execution::seq (sequential), par (parallel), par_unseq (parallel + vectorised). The policy is a hint — the implementation decides how to exploit it.
Common mistakes
- ✗Assuming
parautomatically makes the code thread-safe — you must still guard shared state - ✗Using lambdas that capture by reference with
par— concurrent access to the same variable is a data race - ✗Expecting
parto always be faster — for small inputs the thread creation overhead dominates
Follow-up questions
- →What library does libstdc++ / MSVC use to implement parallel execution policies?
- →How do C++20 ranges algorithms interact with execution policies?
SeniorCodeRareImplement a hash function and collision handling
Implement a hash function and collision handling
A good hash function distributes keys uniformly and is fast to compute. FNV-1a and djb2 are classic non-cryptographic hashes. Collision resolution: separate chaining (linked list per bucket) or open addressing (linear/quadratic probing, double hashing). std::unordered_map uses separate chaining in most implementations.
Common mistakes
- ✗Using a bad hash function that clusters values — leads to O(n) lookups in the worst case
- ✗Not handling growing the table (rehashing) when load factor exceeds threshold
- ✗Using a cryptographic hash (SHA, MD5) for a hash table — far too slow
Follow-up questions
- →What is the load factor and how does it affect performance?
- →Compare open addressing and separate chaining for cache performance.
SeniorDesignRareGiven each guest's check-in and check-out dates (check-in strictly before check-out, so every guest stays at least one night), design an algorithm that finds the maximum number of guests staying in the hotel at the same time. On any shared day a departing guest leaves before a new guest arrives. Describe your data structures, the time complexity, and how you handle the tie-break when intervals touch at one point.
Given each guest's check-in and check-out dates (check-in strictly before check-out, so every guest stays at least one night), design an algorithm that finds the maximum number of guests staying in the hotel at the same time. On any shared day a departing guest leaves before a new guest arrives. Describe your data structures, the time complexity, and how you handle the tie-break when intervals touch at one point.
Use a sweep line: split each stay into a +1 check-in and a −1 check-out event, sort by time, and sweep keeping a running count whose maximum is the answer. At a shared time process check-outs before check-ins. O(N log N) for the sort.
Common mistakes
- ✗Getting the tie-break wrong at a shared day, counting a departure and arrival as simultaneous
- ✗Using a day-indexed array that blows up when dates span a huge range
- ✗Forgetting that check-out frees a slot, so the −1 must be applied at the right moment
Follow-up questions
- →How would you also report which day (or days) had the peak occupancy?
- →What changes if you must support streaming stays added one at a time?
SeniorCodeRareLongest run of 1s after deleting exactly one element
Longest run of 1s after deleting exactly one element
Sliding window allowing at most one zero inside it; the answer is the largest window size minus one (one element is always removed). When there are no zeros, that minus-one still applies, giving L-1 for an all-ones array. One pass, O(n), O(1).
Open full question →Common mistakes
- ✗Returning the window size without subtracting one for the mandatory deletion
- ✗Failing the all-ones case where one element must still be removed (answer L-1)
- ✗Allowing more than one zero in the window
Follow-up questions
- →How does 'exactly one' deletion differ from 'at most one' for an all-ones array?
- →How would the window generalize to deleting up to k elements?
SeniorCodeRareRestore all valid IPv4 addresses from a digit string
Restore all valid IPv4 addresses from a digit string
Backtrack: place three dots splitting the string into four octets. At each step try a 1-, 2-, or 3-digit octet, accepting it only if its value is 0–255 with no leading zero (unless exactly "0"). When all four octets consume the whole string, record the address.
Common mistakes
- ✗Allowing leading zeros like
01or00in an octet - ✗Accepting octet values above 255
- ✗Not requiring all four octets to consume the entire string
Follow-up questions
- →Which inputs produce zero valid addresses?
- →How would you extend this to IPv6 grouping?
SeniorTheoryRareWhat improvements did std::search get in C++17?
What improvements did std::search get in C++17?
C++17 added a searcher-based overload std::search(first, last, searcher) with three standard searchers: default_searcher, boyer_moore_searcher, boyer_moore_horspool_searcher. The searcher preprocesses the pattern once in its constructor.
Common mistakes
- ✗Creating a new searcher object inside a loop — the preprocessing advantage is lost; create it once
- ✗Using Boyer-Moore for very short patterns — the preprocessing overhead is not amortised
- ✗Expecting
boyer_moore_searcherto work on non-random-access iterators — it requires them
Follow-up questions
- →What is the time complexity of Boyer-Moore search in the best vs worst case?
- →How would you write a custom searcher conforming to the C++17 interface?
SeniorCodeRareImplement merge sort or introsort with correctness discussion
Implement merge sort or introsort with correctness discussion
Merge sort guarantees O(n log n) and is stable, making it suitable for linked lists and external sorting. Introsort (quicksort + heapsort fallback + insertion sort for small ranges) is what std::sort uses: O(n log n) guaranteed, in-place, but not stable.
Common mistakes
- ✗Not knowing why
std::sortuses introsort instead of pure quicksort - ✗Implementing merge sort with O(n log n) extra space when an in-place version is asked
- ✗Ignoring the insertion sort optimisation for small subarrays (< 16 elements)
Follow-up questions
- →What is the depth threshold at which introsort switches from quicksort to heapsort?
- →How does
std::stable_sortdiffer fromstd::sortin terms of algorithm and complexity?