Data Structures & Algorithms Roadmap

From Big-O to dynamic programming: 33 topics in four stages, each with what to learn, free resources, something to build from scratch and a hand-picked set of easy, medium and hard LeetCode problems, 288 in all, every one free to solve. Tick problems as you solve them to track your progress. Filter by Interview essentials to focus on coding interviews, or Competitive programming for the full set.

The roadmap in detail

Everything from the map above, on one page. Dashed topics are optional. The hours are rough estimates for learning the idea plus solving the problems. Problems in each list go from easy to hard: solve them in order.

Stage 1 · Beginner Foundations

Learn to measure an algorithm's cost, then master the patterns that solve most array and string problems: hashing, two pointers, sliding windows and prefix sums. Finish with recursion, which everything in later stages builds on.

Big-O and your language's toolkit

Big-O notation describes how an algorithm's running time or memory grows with the input size n, ignoring constants. O(1) is constant, O(log n) halves the work each step (binary search), O(n) touches each item once, O(n log n) is good sorting, O(n²) is a nested loop and O(2ⁿ) tries every subset. Learn to read the complexity of your own code, because interviewers always ask, and to use the input limits to guess the target: n ≤ 10⁵ usually means O(n log n) or better, n ≤ 20 hints at exponential search.

Pick one language and learn its built-in data structures well. Python (lists, dicts, sets, collections, heapq, bisect), Java (ArrayList, HashMap, ArrayDeque, PriorityQueue, TreeMap) and C++ (the STL) are all fine. You'll use them in every problem.

What to learn

  • Big-O, Big-Ω and Big-Θ; best, average and worst case
  • Time vs space complexity, including the recursion call stack
  • Common classes: O(1), O(log n), O(n), O(n log n), O(n²), O(2ⁿ), O(n!)
  • Amortised cost (why appending to a dynamic array is O(1) on average)
  • Estimating the target complexity from input limits (about 10⁸ simple operations per second)
  • Your language's lists, maps, sets, deques, heaps and sorted containers, and their costs

Free resources

Practice

Write five short functions (a single loop, a nested loop, a halving loop, a recursive Fibonacci and a sort-then-scan) and state each one's time and space complexity. Then time them in your language for n = 10³, 10⁴ and 10⁵ and check the growth matches.

Tools on this site

Arrays and strings

An array stores items next to each other in memory, so reading any index is O(1), but inserting or deleting in the middle shifts everything after it (O(n)). Dynamic arrays (Python list, Java ArrayList, C++ vector) grow by doubling. Strings are arrays of characters, and in Python and Java they're immutable: building one with += in a loop is O(n²), so use a list and join, or a StringBuilder.

Many array problems are solved in place by overwriting with a write pointer, or by using the array's own indices as a hash (as in First Missing Positive).

What to learn

  • Indexing, iteration, slicing and their costs
  • In-place updates with a read pointer and a write pointer
  • Reversal tricks (rotate an array by reversing three times)
  • Character counting with a 26-slot array
  • Efficient string building; ASCII and Unicode basics
  • 2D arrays (matrices): row/column traversal and bounds checks

Free resources

Practice problems

Build it yourself

Implement a dynamic array class with append, get, set and pop that doubles its capacity when full, and count how many element copies 1,000,000 appends cause.

Hash maps and sets

A hash map turns a key into an array index with a hash function, giving O(1) average lookup, insert and delete. It's the single most useful tool in interviews: whenever you're searching for something you've seen before (a complement, a duplicate, a count), a map or set usually turns O(n²) into O(n). Collisions are handled by chaining or open addressing, and the table resizes when it fills up.

What to learn

  • How hashing works: hash functions, buckets, collisions and load factor
  • Sets for membership and de-duplication
  • Maps for counting (frequency tables) and for grouping by a key
  • Choosing a key: sorted string, character-count tuple, (row, col) pair
  • The "seen so far" pattern (Two Sum)
  • Worst-case O(n) behaviour and why keys must be immutable

Free resources

Practice problems

Build it yourself

Implement a hash map with separate chaining (put, get, remove) that doubles its bucket array when the load factor passes 0.75.

Two pointers

Two indices move through the data instead of one, so a pair search that would take O(n²) takes O(n). The two common shapes are opposite ends (start at both ends of a sorted array and move inward depending on the comparison, as in Two Sum II and Container With Most Water) and same direction (a fast reader and a slow writer, as in Move Zeroes). Sorting first is often what makes two pointers possible, as in 3Sum.

What to learn

  • Opposite-end pointers on sorted input
  • Fast/slow (reader/writer) pointers for in-place filtering
  • Sort first, then two pointers (3Sum), and skipping duplicates
  • Dutch national flag: three-way partitioning
  • Proving why moving a pointer never skips the answer

Free resources

Practice problems

Build it yourself

Write a function that merges two sorted arrays into one sorted array in O(n + m), then a second that returns their intersection without a hash set.

Sliding window

A window is a contiguous range [left, right] that slides across an array or string. A fixed-size window adds the new item and removes the old one each step. A variable-size window grows by moving right and shrinks by moving left whenever it becomes invalid, keeping counts in a hash map. Each index enters and leaves once, so it's O(n). Use it for "longest/shortest/count of subarrays or substrings with property X", as long as the property doesn't break when the window shrinks (negative numbers often break it; then use prefix sums).

What to learn

  • Fixed-size windows: running sum or count
  • Variable windows: expand right, shrink left while invalid
  • Tracking window contents with a counter map and a "matched" count
  • Longest-valid vs shortest-valid window templates
  • "At most K" tricks: exactly(K) = atMost(K) − atMost(K − 1)

Free resources

Practice problems

Build it yourself

Write one reusable template for "longest substring where the window satisfies a condition" and use it, unchanged except for the condition, to solve three of the problems above.

Prefix sums

Store prefix[i] = the sum of the first i items and any range sum becomes one subtraction: sum(l..r) = prefix[r+1] − prefix[l]. Combined with a hash map of prefix sums seen so far, it counts subarrays with a given sum in O(n), even with negative numbers, where a sliding window fails. The same idea works in 2D (sum of any rectangle), with XOR instead of addition, and in reverse as a difference array for many range updates.

What to learn

  • 1D prefix sums and the off-by-one convention (prefix[0] = 0)
  • Prefix sum + hash map: count subarrays summing to k
  • Prefix sums modulo k (divisibility problems)
  • 2D prefix sums for rectangle queries
  • Difference arrays for range updates; prefix and suffix products

Free resources

Practice problems

Build it yourself

Given 10⁵ updates "add v to every element from l to r" on an array of 10⁵ zeros, produce the final array in O(n + q) with a difference array, and check it against a slow version on random inputs.

Recursion

A recursive function solves a problem by calling itself on a smaller version of it. Every recursion needs a base case that stops it and a step that makes progress towards it. Trust the recursive call to do its job ("the leap of faith") and only think about one level. Recursion is the foundation for trees, backtracking, divide and conquer, DFS and dynamic programming, so get comfortable now. Know that each call uses stack space, and that very deep recursion overflows (Python's default limit is 1,000 frames).

What to learn

  • Base case, recursive case and the call stack
  • Drawing the recursion tree to find time complexity
  • Divide and conquer (fast power: xⁿ in O(log n))
  • Memoisation: caching results of repeated calls
  • Converting recursion to iteration with an explicit stack

Free resources

Practice problems

Build it yourself

Write Fibonacci three ways (plain recursion, memoised, iterative), count the calls each makes for n = 30, and solve Tower of Hanoi for 4 discs, printing every move.

Am I ready? Stage 1 quiz

A loop runs while n > 1 and sets n = n / 2 each time. What is its time complexity?

  1. O(n)
  2. O(n / 2)
  3. O(log n)
  4. O(1)

Halving n each step takes about log₂ n steps to reach 1.

You need to know whether any two numbers in an unsorted array add up to a target. What is the fastest usual approach?

  1. One pass with a hash set of numbers seen so far
  2. Two nested loops
  3. Binary search for every element without sorting
  4. Recursion over all subsets

For each number, check whether target − number is in the set: O(n) time and O(n) space.

Count subarrays whose sum equals k, when the array can contain negative numbers. Which technique fits?

  1. Variable sliding window
  2. Two pointers from both ends
  3. Sorting, then binary search
  4. Prefix sums with a hash map of counts

Negative numbers mean shrinking the window doesn't reliably lower the sum, so a sliding window breaks. Prefix sums still work.

Which problem is a natural fit for a variable-size sliding window?

  1. Find the median of an array
  2. Longest substring without repeating characters
  3. Reverse a string
  4. Check whether a number is prime

Grow the window to the right, and shrink it from the left whenever a character repeats.

What does every correct recursive function need?

  1. A global variable
  2. Memoisation
  3. A base case that stops the recursion, and progress towards it
  4. At least two recursive calls

Without a reachable base case, the recursion never ends and overflows the stack.

Stage 2 · Beginner → Intermediate Core data structures

The structures every programmer is expected to know: linked lists, stacks, queues, trees and heaps, plus binary search, sorting and interval problems.

Linked lists

A linked list is a chain of nodes, each holding a value and a pointer to the next (and, in a doubly linked list, the previous) node. Inserting or removing a node you already hold is O(1), but finding the i-th node is O(n). Interview problems are mostly about careful pointer manipulation: draw the nodes on paper, use a dummy head node to avoid special cases, and use fast and slow pointers to find the middle or detect a cycle.

What to learn

  • Singly vs doubly linked lists; insert, delete and traverse
  • Reverse a list iteratively and recursively
  • Dummy (sentinel) head nodes
  • Fast and slow pointers: middle node, cycle detection (Floyd)
  • Merging and splitting lists

Free resources

Practice problems

Build it yourself

Implement a doubly linked list with push_front, push_back, pop_front, pop_back, remove(node) and reverse, with sentinel head and tail nodes, plus tests for empty and one-element lists.

Stacks and monotonic stacks

A stack is last in, first out: push and pop at the top in O(1). Reach for one whenever the most recent unfinished thing matters: matching brackets, undo, evaluating expressions, or simulating recursion. A monotonic stack keeps its items in increasing or decreasing order by popping anything that breaks the order. It answers "next greater/smaller element" for every index in one O(n) pass, and is the key to Daily Temperatures and Largest Rectangle in Histogram.

What to learn

  • Push, pop and peek; using an array or deque as a stack
  • Bracket matching and expression evaluation (Reverse Polish notation)
  • Stacks of (value, extra info) pairs, as in Min Stack
  • Monotonic stack: next greater and next smaller element
  • Storing indices rather than values to measure distances and widths

Free resources

Practice problems

Build it yourself

Write a calculator that evaluates expressions such as 3 + 4 * (2 - 1) with two stacks (operands and operators), respecting precedence and parentheses.

Tools on this site

Queues and deques

A queue is first in, first out: add at the back, remove from the front. It's the engine behind breadth-first search and any "process in arrival order" simulation. A deque (double-ended queue) adds and removes at both ends in O(1). A monotonic deque keeps the maximum (or minimum) of a sliding window available in O(1), giving O(n) for Sliding Window Maximum. Use your language's deque (Python collections.deque, Java ArrayDeque): removing from the front of a plain array is O(n).

What to learn

  • Queue operations and why list.pop(0) is slow
  • Circular buffers (ring queues)
  • Deques, and stacks/queues built from each other
  • Monotonic deque for sliding-window max/min
  • Queues in simulations and in BFS (preview of stage 3)

Free resources

Practice problems

Build it yourself

Implement a fixed-capacity ring buffer queue with enqueue, dequeue, front, isFull and isEmpty, using one array and two indices.

Sorting algorithms

You'll mostly call the built-in sort (O(n log n)), but you need to know how the main algorithms work and what their trade-offs are. Merge sort is stable and always O(n log n) but needs O(n) extra space; its merge step also counts inversions. Quicksort is in place and fast on average but O(n²) in the worst case without a random pivot; its partition step powers quickselect (k-th smallest in O(n) on average). Counting and bucket sort beat O(n log n) when values fall in a small range. Custom comparators turn many problems into "sort, then scan".

What to learn

  • Bubble, insertion and selection sort, and why they're O(n²)
  • Merge sort, and counting inversions during the merge
  • Quicksort, partition schemes and quickselect
  • Counting sort, bucket sort and radix sort
  • Stability, and sorting with custom comparators or keys

Free resources

Practice problems

Build it yourself

Implement merge sort, quicksort with a random pivot and counting sort, then compare their running times on random, already-sorted and many-duplicates arrays of 10⁶ items.

Tools on this site

Intervals

Interval problems (meetings, bookings, ranges) almost always start by sorting by start time (or by end time for greedy selection), then sweeping once while comparing each interval with the last one kept. Two intervals [a, b] and [c, d] overlap when a <= d and c <= b. For "how many overlap at once?" use a sweep line: turn each interval into +1 at its start and −1 at its end, sort the events and keep a running count.

What to learn

  • The overlap condition and merging overlapping intervals
  • Inserting an interval into a sorted, non-overlapping list
  • Sort by end time to keep the most non-overlapping intervals
  • Sweep line / event counting for maximum overlap
  • Intersections of two interval lists with two pointers

Free resources

Practice problems

Build it yourself

Given a list of meetings, compute the minimum number of rooms needed with a sweep line, and again with a min-heap of end times. Check that both agree on random inputs.

Tools on this site

Binary trees

A binary tree is a set of nodes where each has at most two children. Almost every tree problem is a short recursive function: compute something for the left subtree, the right subtree, and combine. Learn the four traversals: preorder (node, left, right), inorder (left, node, right), postorder (left, right, node), each by recursion and with an explicit stack, and level order with a queue. Many hard problems (Maximum Path Sum) return one value up the tree while updating a global answer.

What to learn

  • Terminology: root, leaf, height, depth, balanced, complete, full
  • Pre-, in- and postorder traversal, recursive and iterative
  • Level-order traversal (BFS) and processing level by level
  • The "return a value, update a global answer" pattern
  • Rebuilding a tree from traversals; serialising a tree to a string
  • Lowest common ancestor

Free resources

Practice problems

Build it yourself

Write a function that prints any binary tree sideways in the terminal, then write all four traversals iteratively and check them against the recursive versions.

Tools on this site

Binary search trees

In a binary search tree every node is greater than everything in its left subtree and smaller than everything in its right subtree, so search, insert and delete take O(height), and an inorder traversal visits the values in sorted order. A random BST has height about log n, but inserting sorted data makes it a linked list (O(n)). Self-balancing trees (AVL, red-black) fix that, and they're what's behind Java's TreeMap and C++'s std::map. You won't implement one in an interview, but you should know they exist and what they guarantee.

What to learn

  • The BST property and why inorder gives sorted order
  • Search, insert and delete (the three cases of delete)
  • Validating a BST with min/max bounds
  • Balanced trees (AVL, red-black) at a conceptual level
  • Ordered maps/sets in your language: floor, ceiling, range queries

Free resources

Practice problems

Build it yourself

Implement a BST with insert, delete, search, min, max and an inorder iterator. Insert 1..1000 in sorted order and in random order and compare the heights.

Heaps and priority queues

A heap is a complete binary tree stored in an array where every parent is smaller (min-heap) or larger (max-heap) than its children. You get the smallest item in O(1) and push or pop in O(log n). Use it whenever you repeatedly need "the next smallest/largest": top-K problems (keep a heap of size K), merging K sorted lists, scheduling, and Dijkstra's algorithm. The two-heap trick (a max-heap for the lower half, a min-heap for the upper half) gives a running median.

What to learn

  • Array layout: children of i at 2i+1 and 2i+2
  • Sift up, sift down, and O(n) heapify
  • Your language's heap: Python heapq (min-heap only), Java PriorityQueue
  • Top-K with a size-K heap in O(n log K)
  • K-way merge and the two-heaps running median

Free resources

Practice problems

Build it yourself

Implement a min-heap class (push, pop, peek, heapify) and use it to write heapsort. Test it against your language's sort on random arrays.

Am I ready? Stage 2 quiz

How do you detect a cycle in a linked list using O(1) extra space?

  1. Store every visited node in a hash set
  2. Move a slow pointer one step and a fast pointer two steps; they meet if there is a cycle
  3. Reverse the list twice
  4. Count the nodes

Floyd's tortoise and hare: in a cycle the fast pointer gains one step per move and must catch the slow one.

For each day you want the number of days until a warmer temperature. Which structure gives O(n)?

  1. A min-heap
  2. A hash map
  3. A queue
  4. A monotonic stack of indices

Keep indices of days still waiting for a warmer day; each new day pops every cooler day it resolves.

Which traversal of a binary search tree visits the values in sorted order?

  1. Inorder
  2. Preorder
  3. Postorder
  4. Level order

Left subtree (smaller), node, right subtree (larger): exactly ascending order.

What is the best way to find the K largest of n numbers when K is much smaller than n?

  1. Sort everything, O(n log n)
  2. A max-heap of all n numbers
  3. A min-heap that never holds more than K numbers, O(n log K)
  4. Binary search

The min-heap's top is the smallest of the current K largest; anything bigger replaces it.

"Find the minimum eating speed that finishes all piles within h hours." Why can you binary search on the speed?

  1. The piles are sorted
  2. Feasibility is monotonic: if speed s works, every faster speed works too
  3. Speeds are always powers of two
  4. Binary search works on any problem

A monotonic yes/no condition over the answer range is exactly what binary search on the answer needs.

Stage 3 · Intermediate Core algorithms

The algorithm families behind most medium and hard problems: backtracking, graphs, greedy choices and dynamic programming, plus tries and bit tricks.

Backtracking

Backtracking builds a solution one choice at a time and undoes the last choice when it leads nowhere. It systematically explores every subset, permutation or combination, so it's exponential, which is fine for small inputs (n ≤ 20 or so). Every backtracking solution has the same shape: if the current path is complete, record it; otherwise, for each valid choice, choose, recurse, un-choose. Speed comes from pruning: stop exploring a branch as soon as it can't succeed.

What to learn

  • The choose / explore / un-choose template
  • Subsets, permutations and combinations, and their counts (2ⁿ, n!, C(n, k))
  • Skipping duplicates after sorting
  • Pruning with constraints (sums, used sets, board rules)
  • Backtracking on grids (Word Search) and boards (N-Queens)

Free resources

Practice problems

Build it yourself

Write a Sudoku solver, then add a counter of recursive calls and see how much smarter cell ordering (fewest candidates first) cuts it.

Graphs: BFS and DFS

A graph is a set of nodes connected by edges, directed or undirected, weighted or not. Store it as an adjacency list (a map from node to neighbours). A grid is a graph too: each cell connects to its four neighbours. Depth-first search goes as deep as possible first (recursion or a stack) and is good for connectivity, counting components and cycle detection. Breadth-first search explores level by level with a queue and finds the shortest path in an unweighted graph. Always keep a visited set, and mark nodes when you add them to the queue, not when you pop them.

What to learn

  • Adjacency lists vs adjacency matrices vs edge lists
  • DFS recursively and iteratively; counting connected components
  • BFS for shortest paths in unweighted graphs, and multi-source BFS
  • Grids as graphs: directions arrays and bounds checks
  • Cycle detection; bipartite checking with two colours

Free resources

Practice problems

Build it yourself

Write a maze solver: read a grid of walls and spaces, find the shortest path from S to E with BFS and print the maze with the path drawn in.

Topological sort

A topological order lists the nodes of a directed acyclic graph (DAG) so every edge points forwards: each task comes after everything it depends on. Kahn's algorithm repeatedly takes nodes with no remaining incoming edges (in-degree 0) from a queue. If it can't place every node, the graph has a cycle, which is how Course Schedule detects impossible plans. The DFS version adds a node after visiting all its descendants and reverses the result. Build systems, package managers and spreadsheet recalculation all work this way.

What to learn

  • DAGs, dependencies and in-degrees
  • Kahn's algorithm (BFS) and cycle detection with it
  • DFS-based topological sort with three node colours
  • Longest path / earliest finish time in a DAG

Free resources

Practice problems

Build it yourself

Write a tiny build tool: read lines like app: lib utils, print a valid build order, and report the cycle if there is one.

Tools on this site

Union-Find (disjoint set union)

Union-Find keeps track of which items are in the same group while groups are merged. find(x) returns the group's representative and union(a, b) merges two groups. With path compression and union by rank/size, both are effectively O(1). It's ideal when connections arrive one at a time ("are these two connected yet?", "does this edge create a cycle?") and it powers Kruskal's minimum spanning tree algorithm.

What to learn

  • Parent array, find and union
  • Path compression and union by rank or size
  • Counting components and detecting a redundant edge
  • Mapping strings or coordinates to integer ids

Free resources

Practice problems

Build it yourself

Write a reusable DSU class with path compression and union by size, then use it to count islands in a grid as land cells are added one by one.

Shortest paths and spanning trees

When edges have weights, BFS no longer finds shortest paths. Dijkstra's algorithm does, for non-negative weights: always expand the closest unfinished node, using a min-heap, in O(E log V). Bellman-Ford handles negative weights and limited edge counts (Cheapest Flights Within K Stops) in O(V·E), and Floyd-Warshall computes all pairs in O(V³) for small graphs. A minimum spanning tree connects all nodes with the least total weight: Kruskal sorts edges and joins with Union-Find, Prim grows the tree with a heap.

What to learn

  • Dijkstra with a priority queue, and skipping stale heap entries
  • Why Dijkstra fails with negative edges
  • Bellman-Ford and shortest paths with at most K edges
  • Floyd-Warshall for all-pairs distances
  • Minimum spanning trees with Kruskal and Prim

Free resources

Practice problems

Build it yourself

Model a few cities and road distances, then print the shortest route between two of them with Dijkstra, including the path (store each node's predecessor).

Greedy algorithms

A greedy algorithm makes the locally best choice at each step and never reconsiders it. It's fast and simple, but only correct when you can argue that the greedy choice is always part of some optimal answer (the "exchange argument": swapping any other choice for the greedy one doesn't make things worse). Typical greedy moves: sort by end time, always take the cheapest available option, or track the furthest reachable position. If you can't find a convincing argument, or a small counter-example breaks it, the problem probably needs dynamic programming.

What to learn

  • When greedy works: greedy-choice property and exchange arguments
  • Kadane's algorithm for the maximum subarray
  • Reachability greedy (Jump Game) and gas-station style balances
  • Greedy with sorting or a heap
  • Finding counter-examples quickly with brute force on small inputs

Free resources

Practice problems

Build it yourself

Write a greedy coin-change function and a brute-force one, and search for a coin system and amount where greedy gives the wrong answer (try coins 1, 3 and 4).

Dynamic programming: 1D

Dynamic programming solves problems made of overlapping subproblems by solving each one once and storing the result. Find a state (for example "the best answer for the first i houses"), a recurrence that builds it from smaller states, and the base cases. Write it top-down (recursion plus memoisation) first, because it's closest to how you think, then convert to bottom-up (a table filled in order), which can often be shrunk to a few variables. Signs of DP: "count the ways", "minimum/maximum", "is it possible", with choices at each step.

What to learn

  • Overlapping subproblems and optimal substructure
  • Top-down memoisation vs bottom-up tabulation
  • Defining the state and the recurrence in words before coding
  • Classic 1D patterns: climbing stairs, house robber, coin change, word break
  • Longest increasing subsequence in O(n²) and O(n log n)
  • Space optimisation: keep only the last one or two rows

Free resources

Practice problems

Build it yourself

Solve Coin Change three ways (plain recursion, memoised, bottom-up), time them for amount = 30, 300 and 3,000, and explain the difference.

Dynamic programming: 2D

When the state needs two numbers, the table becomes a grid. The big families: grid paths (ways or cost to reach each cell), two sequences (dp[i][j] = answer for the first i characters of one string and first j of the other, as in Longest Common Subsequence and Edit Distance), and knapsack (items × capacity, as in Coin Change II and Target Sum). Draw a small table by hand and fill it in before you code; the recurrence usually becomes obvious. Many of these also have state-machine versions (Stock with Cooldown).

What to learn

  • Grid DP: unique paths and minimum path sum
  • Two-sequence DP: LCS, edit distance, interleaving strings
  • 0/1 knapsack vs unbounded knapsack, and the loop order that distinguishes them
  • State-machine DP (holding / not holding / cooldown)
  • Reducing a 2D table to one row

Free resources

Practice problems

Build it yourself

Write a small "diff" tool: compute the LCS of two lists of lines and print them with + and - markers like diff does.

Tools on this site

Tries

A trie (prefix tree) stores strings character by character, so all words sharing a prefix share a path. Insert and lookup cost O(length of the word), regardless of how many words are stored, and "all words starting with…" is a walk to the prefix node. Tries power autocomplete, spell-checkers and Word Search II, where they let one DFS over the grid search for every word at once. A binary trie over the bits of numbers solves maximum-XOR problems.

What to learn

  • Node structure: children map or 26-slot array, end-of-word flag
  • Insert, search and startsWith
  • Wildcard search with DFS through the trie
  • Combining a trie with grid backtracking
  • Binary tries for XOR problems

Free resources

Practice problems

Build it yourself

Load a word list into a trie and build a command-line autocomplete that prints the top 5 completions for whatever prefix you type.

Bit manipulation

Integers are bit patterns, and bitwise operators (&, |, ^, ~, <<, >>) work on all bits at once. A few identities solve many problems: x ^ x = 0 (so XOR-ing everything leaves the single unpaired number), x & (x − 1) clears the lowest set bit, and x & −x isolates it. An integer can also be a bitmask representing a subset of up to ~20 items, which you'll use for bitmask DP later. Learn how negative numbers are stored (two's complement).

What to learn

  • AND, OR, XOR, NOT and shifts; test, set, clear and toggle bit i
  • XOR tricks: finding the unique or missing number
  • Counting set bits and x & (x − 1)
  • Two's complement and overflow in fixed-width integers
  • Bitmasks as subsets: iterating over all 2ⁿ subsets

Free resources

Practice problems

Build it yourself

Write a function that prints every subset of a 4-item list by counting from 0 to 15 and reading the bits, then a set of permission flags (read/write/execute) stored in one integer.

Tools on this site

Am I ready? Stage 3 quiz

Which algorithm finds the shortest path in an unweighted graph?

  1. Breadth-first search
  2. Depth-first search
  3. Topological sort
  4. Union-Find

BFS visits nodes in order of distance, so the first time it reaches a node is along a shortest path.

Kahn's algorithm ends with some nodes never placed in the order. What does that mean?

  1. The graph is disconnected
  2. The graph is undirected
  3. The graph has a cycle
  4. The graph has negative edges

Nodes on a cycle never reach in-degree 0, so they're never added. That's how Course Schedule spots impossible plans.

Why can't you use Dijkstra's algorithm when some edges have negative weights?

  1. It becomes O(n³)
  2. It finalises a node as soon as it's the closest, but a later negative edge could make a cheaper path
  3. Heaps can't store negative numbers
  4. It only works on trees

Dijkstra's correctness relies on paths never getting cheaper as they get longer. Use Bellman-Ford instead.

In Longest Common Subsequence, what does dp[i][j] usually mean?

  1. Whether character i equals character j
  2. The number of common characters overall
  3. The position of the j-th match
  4. The LCS length of the first i characters of one string and the first j of the other

Two-sequence DP indexes by prefix lengths, building from dp[i-1][j-1], dp[i-1][j] and dp[i][j-1].

Every number in an array appears twice except one. What finds it in O(n) time and O(1) space?

  1. Sorting
  2. A hash set
  3. XOR all the numbers together
  4. Binary search

Pairs cancel out because x ^ x = 0, leaving the single number.

Stage 4 · Advanced Advanced and interview-ready

Harder DP, designing data structures, and optional topics for competitive programming and top-tier interviews. Finish with a plan for practising like the real thing.

Advanced dynamic programming

Once 1D and 2D DP feel natural, learn the forms that show up in hard problems. DP on trees computes each node's answer from its children (House Robber III). Interval DP solves dp[l][r] from smaller intervals by trying every split point (Burst Balloons). Bitmask DP uses a subset as part of the state when n ≤ 20 (visiting all nodes, assigning people to tasks). State-machine DP tracks a small set of modes, as in the stock problems with K transactions.

What to learn

  • DP on trees with post-order traversal
  • Interval DP and choosing the "last" element to remove or split on
  • Bitmask DP over subsets
  • State-machine DP with K transactions
  • Reconstructing the actual solution, not just its value

Free resources

Practice problems

Build it yourself

Solve the travelling salesman problem for 12 random cities with bitmask DP, and compare its speed with trying all permutations for 9 cities.

Designing data structures

"Design" problems ask you to build a class with several operations, each within a time limit, by combining the structures you already know. The LRU cache is the classic: a hash map for O(1) lookup plus a doubly linked list for O(1) recency updates. Start by listing each operation and its required complexity, then pick structures that meet all of them, and say the trade-offs out loud. This is also the bridge to system design interviews.

What to learn

  • Combining a hash map with a linked list, heap or array
  • Lazy deletion and versioned data (snapshots)
  • Picking structures from the required time complexity of each operation
  • Writing clean classes and testing them with operation sequences

Free resources

Practice problems

Build it yourself

Implement an LRU cache and an LFU cache with O(1) operations, and test both by replaying 100,000 random operations against a slow but obviously correct version.

Math, matrices and number theory

Some problems are mostly maths: greatest common divisor (Euclid's algorithm), primes with the Sieve of Eratosthenes, modular arithmetic (answers "modulo 10⁹ + 7"), fast exponentiation and counting combinations. Matrix problems (rotate, spiral order, set zeroes) test careful index handling. Geometry problems usually need slopes or cross products, using integers or fractions to avoid floating-point errors.

What to learn

  • GCD, LCM and Euclid's algorithm
  • Sieve of Eratosthenes and primality checks
  • Modular arithmetic and modular inverse
  • Combinations, Pascal's triangle and counting
  • Matrix rotation and layer-by-layer traversal

Free resources

Practice problems

Build it yourself

Write a function that computes C(n, k) mod 10⁹ + 7 for n up to 10⁶ using precomputed factorials and modular inverses.

Segment trees and Fenwick trees

Prefix sums answer range queries in O(1) but break as soon as the array changes. A Fenwick tree (binary indexed tree) supports point updates and prefix sums in O(log n) with a few lines of code. A segment tree is more general: any associative operation (sum, min, max, gcd) over a range, with point updates, and with lazy propagation for range updates too. Common in competitive programming and in hard "count smaller elements" problems.

What to learn

  • Fenwick tree: update and prefix query using the lowest set bit
  • Segment tree: build, point update, range query
  • Lazy propagation for range updates
  • Coordinate compression for large value ranges

Free resources

Practice problems

Build it yourself

Implement a segment tree with lazy propagation that supports "add v to range" and "sum of range", and test it against a plain array on random operations.

String algorithms

Naive substring search is O(n·m). KMP uses a prefix function (the longest proper prefix that is also a suffix) to search in O(n + m) without ever moving backwards. The Z-function gives similar results. Rabin-Karp compares rolling hashes of windows, which also makes "find a repeated substring of length L" fast, especially combined with binary search on L.

What to learn

  • Prefix function and the KMP algorithm
  • Z-function
  • Rolling hashes (Rabin-Karp) and dealing with collisions
  • Binary search on length + hashing

Free resources

Practice problems

Build it yourself

Implement KMP and Rabin-Karp and compare them with your language's built-in find on a 10 MB text full of near-matches (for example "aaaa…ab").

Tools on this site

Advanced graph algorithms

Beyond BFS, DFS and Dijkstra there's a set of DFS-based algorithms worth knowing. Tarjan's algorithm finds bridges and articulation points (edges and nodes whose removal disconnects the graph) using discovery times and low-links. Strongly connected components group nodes in a directed graph that can all reach each other. An Eulerian path uses every edge exactly once (Hierholzer's algorithm, as in Reconstruct Itinerary). And many graphs are hidden: in Bus Routes, the nodes are buses, not stops.

What to learn

  • Bipartite graphs and two-colouring
  • Bridges and articulation points with low-link values
  • Strongly connected components (Tarjan or Kosaraju)
  • Eulerian paths with Hierholzer's algorithm
  • Modelling a problem as a graph when nodes aren't obvious

Free resources

Practice problems

Build it yourself

Model a small computer network and use bridge-finding to list every cable whose failure would split it in two.

Tools on this site

Interview practice plan

Knowing the patterns isn't the same as solving a new problem in 45 minutes while talking. In a coding interview: clarify the problem and edge cases, give a brute-force idea first, improve it while stating complexities, code it cleanly, then test with a small example and edge cases. Practise exactly that, out loud and timed. Re-solve problems you got wrong a week later, and keep a short log of the pattern each problem used: that log becomes your revision sheet.

What to learn

  • A consistent approach: clarify, brute force, optimise, code, test
  • Solve problems timed (about 20 min medium, 40 min hard) and out loud
  • Spaced repetition: re-solve missed problems after 3 days and after 2 weeks
  • Do a curated list end to end (Grind 75 or NeetCode 150)
  • Weekly contests or mock interviews with a friend
  • Behavioural answers: a few stories in the STAR format

Free resources

Practice

Do three mock interviews in one week: one easy and one medium problem each, 45 minutes, explaining as you go. Write down where you got stuck and turn each into a topic to revisit.

Tools on this site

Am I ready? Stage 4 quiz

Which pair of structures gives an LRU cache O(1) get and put?

  1. An array and binary search
  2. A hash map and a doubly linked list
  3. Two heaps
  4. A trie and a stack

The map finds a node instantly; the list moves it to the front or evicts the oldest in O(1).

A problem has n ≤ 16 items and asks for the best order to visit all of them. What does that limit suggest?

  1. A greedy algorithm
  2. Binary search
  3. A segment tree
  4. Bitmask DP over subsets, about 2ⁿ·n² operations

2¹⁶ subsets × 16 × 16 ≈ 17 million steps is fast; trying all 16! orders is not.

You need range sums on an array that also receives point updates. Which structure fits?

  1. A Fenwick tree or segment tree, O(log n) each
  2. A prefix sum array, rebuilt after each update
  3. A hash map
  4. A sorted array

Rebuilding prefix sums is O(n) per update. Fenwick and segment trees keep both operations logarithmic.

What does KMP's prefix function store for each position i?

  1. The number of times the pattern appears
  2. The hash of the first i characters
  3. The length of the longest proper prefix of s[0..i] that is also a suffix of it
  4. The index of the next mismatch

On a mismatch, KMP falls back to that length instead of restarting, which keeps the search linear.

In an interview, what should you usually do right after clarifying the problem?

  1. Start coding the optimal solution silently
  2. Describe a brute-force approach and its complexity, then improve it
  3. Ask for the answer
  4. Write test cases for 20 minutes

A brute force shows you understand the problem and gives a baseline to optimise from, out loud.

How to use this roadmap

  • Learn the idea, then solve the problems in order. Each list goes from easy to hard. Try each problem for 20–30 minutes before looking at a solution. If you do look, close it and write your own version from scratch.
  • Tick problems as you solve them. Solved problems are counted at the top of the page and on each topic. Ticking the "What to learn" items tracks the topic itself: ticking the first marks it as in progress, ticking them all marks it as done. Use Export to move progress to another device or Share progress to send a link.
  • Pick one language and stay with it. Python is the quickest to write in interviews; Java and C++ are just as accepted. What matters is knowing its built-in structures without looking them up.
  • Don't skip the "Build it yourself" tasks. Implementing a hash map, heap or trie once makes the problems that use them much easier to reason about.
  • Optional topics (dashed borders) are for competitive programming and very demanding interviews. They don't count towards the percentage.

How long does it take to learn DSA?

The core path is roughly 190 hours, about six months at an hour a day. If you have an interview soon, use the Interview essentials filter and do the easy and medium problems of each topic first, then come back for the hard ones. All problem links go to LeetCode, and every problem listed is free to solve, no Premium subscription needed. Problems are a means to an end: being able to recognise the pattern in a problem you've never seen is the goal.