The 20 Classic Coding Interview Problems Every Engineer Should Know

June 7, 202611 min read
interview-prepdsaalgorithmsleetcode
The 20 Classic Coding Interview Problems Every Engineer Should Know
TL;DR
  • Two Sum introduces the hash map complement trade-off behind every pair-finding and duplicate-detection problem
  • Kadane's algorithm (Maximum Subarray) teaches the extend-or-restart decision at the core of every optimal-range problem
  • Binary search invariant eliminates off-by-one bugs by keeping the answer inside [low, high] at every step
  • Sliding window (LC 3) proves every element enters and leaves at most once, making O(n) the natural result
  • Topological sort (Course Schedule) is the dependency-resolution pattern behind real build systems and task schedulers
  • LRU Cache fuses a hash map and doubly linked list to hit O(1) on both lookup and eviction simultaneously
  • All 20 form a canonical problem set that explains why 80% of interview problems work the way they do

Some LeetCode problems are practice. Others are canonical. These 20 have lived in CS courses, every serious prep book, and interview loops at every company from Google to a ten-person startup for decades. They earn that status because they teach patterns that explain why 80% of interview problems work the way they do.

If you've "done LeetCode" but can't solve any of these cold and out loud, you haven't done LeetCode. You've watched LeetCode. Different sport.

This list runs foundational to advanced. Start at the beginning even if you think you know the early ones. The details matter.


The Four Problems That Unlock Everything Else

1. Two Sum (LeetCode 1)

Ask any engineer to name a classic problem and this is almost always first. It's the Kevin Bacon of coding problems. Every combination problem, every pairing problem, every "have we seen this before" problem traces back here.

That reputation is earned. Two Sum looks trivial until you see it as a proof of concept for one trade-off: spend O(n) space, buy O(n) time instead of O(n²). Every problem where you find pairs, complements, or "have we seen this before" descends from this one trade-off. Four Sum, subarray target sums, finding duplicates. They all start here.

2. Valid Parentheses (LeetCode 20)

The stack problem that launched a thousand variations. Two-step loop: push opening brackets, pop and verify closing ones. At every point in the scan, the stack holds exactly the unmatched openers in order. That invariant-thinking applies to every stack problem you'll encounter, from expression evaluation to largest rectangle in histogram.

This invariant is more reliable than most documentation. Treasure it.

3. Maximum Subarray (LeetCode 53)

Kadane's algorithm. One pass, O(n): if extending the current subarray helps, extend it. If starting fresh is better, start fresh.

Worth knowing: Kadane designed this in under a minute at a 1984 CMU seminar. The guy makes you feel bad about yourself. The insight that makes this canonical is the decision at each element: is it better as the start of a new subarray or as an extension of the current one? That exact framing reappears in Best Time to Buy and Sell Stock, Maximum Product Subarray, and every problem asking for an optimal contiguous range.

One trap: initialize to nums[0], not 0. All-negative arrays will destroy you otherwise.

4. Binary Search (LeetCode 704)

Not the algorithm. The invariant. Every binary search you write should maintain one property: the answer is always within [low, high]. Once you can state that property, the exit condition and update rules write themselves.

This earns its spot because the off-by-one bugs that haunt people for years come from skipping this step. Fun fact: this exact bug lived in Java's standard library for nine years before Joshua Bloch caught it. Presumably the entire Java team was also winging it.

Binary search on a sorted array is the training problem. Binary search on the answer space (find the minimum capacity, find the smallest valid threshold) is where the real patterns live.


Arrays, Strings, and Pointers

5. Longest Substring Without Repeating Characters (LeetCode 3)

The sliding window pattern has a dozen variants. This one is the clearest introduction. A window defined by two pointers. Expand right. Contract left when the constraint breaks. Track state in a hash map.

Every element enters and leaves the window at most once, making the whole scan O(n) despite looking like O(n²). This is one of those situations where the code looks like it should be slow and isn't. The whole genre of "nested loops that are secretly linear" starts here.

6. 3Sum (LeetCode 15)

Two Sum with an extra element. Sort. Fix one element, use two pointers to find the pair that completes zero. Skip duplicates to avoid repeated triplets.

The problem earns its place not because it's hard but because it forces correct deduplication, which most first attempts get completely wrong. You will skip the wrong element. You will do it with confidence. The sorted order plus skip-duplicates pattern reappears in 4Sum, Triangle, and almost every "find combinations summing to target" problem.

7. Trapping Rain Water (LeetCode 42)

Three valid solutions, each one making the previous look naive. Prefix arrays use O(n) space. Two pointers use O(1). Each forces you to understand the previous one more deeply.

The two-pointer insight: if leftMax is smaller than rightMax, the water at the left pointer is determined solely by leftMax, regardless of what's on the right. You don't need to know the exact right max. You just need to know it's at least as big as leftMax. Once that clicks, a whole class of two-pointer problems becomes readable.

8. Merge Intervals (LeetCode 56)

Sort intervals by start time. Walk the list. If the current interval overlaps the last merged one, extend it. Otherwise push a new interval.

Every scheduling and calendar problem is a merge intervals variant. One subtle trap: max() in the merge step is not optional. A fully contained interval must not shrink the merged end. This seems obvious until you're in an interview and you write end instead of max(end, current_end) and watch it fail on the second test case.


Trees and Graphs

9. Reverse Linked List (LeetCode 206)

Conceptually simple, surprisingly easy to get wrong under pressure. Three pointers. In-place. O(1) space.

Get the reversal automatic and the harder problems get easier by one step. Merge Two Sorted Lists, Reorder List, Palindrome Linked List. They all build on this foundation. Practice it until you can write it without thinking, because you will need the mental budget elsewhere.

10. Linked List Cycle Detection (LeetCode 141/142)

Floyd's algorithm. Fast pointer moves two steps, slow pointer moves one. If there's a cycle, they meet.

The Phase 2 math (resetting one pointer to head and walking both one step at a time to find the cycle start) takes a proof to fully trust. Worth working through once. The fast/slow pattern generalizes to finding the middle of a linked list and detecting if a number sequence loops (LC 202, Happy Number).

11. Binary Tree Level Order Traversal (LeetCode 102)

BFS on a tree. A queue. Process level by level by snapshotting queue size at the start of each level.

This is the prototype for every BFS problem: shortest path in a grid, minimum depth, rotting oranges. The level-size snapshot (save queue.length before processing a level) is a one-line idea worth memorizing cold. Interviewers love BFS problems. This is the one that makes BFS problems not scary.

12. Lowest Common Ancestor (LeetCode 236)

The function either finds a target and returns it, finds both targets in its subtrees and returns the current node as their LCA, or returns null. The insight: a node is an LCA when one target is in its left subtree and the other is in its right.

This problem rewards thinking in terms of what each recursive call promises to return. That skill pays off on every tree problem after this one. If you can articulate the return contract before you code it, you're already in the top half of the room.

13. Number of Islands (LeetCode 200)

DFS or BFS from each unvisited land cell, marking visited cells as water. Count the number of times you start a traversal.

This is the gateway to every grid problem: Pacific Atlantic Water Flow, Surrounded Regions, Max Area of Island. The underlying pattern is connected component counting, and it transfers directly to graph problems (LC 547, Number of Provinces). Grid is graph with implicit edges. Once you see that, the whole category opens up.

14. Course Schedule (LeetCode 207)

Topological sort. Build a directed graph of dependencies. Run Kahn's algorithm (BFS with in-degree tracking) or the DFS three-color approach. Process all nodes and there's no cycle. Nodes remain with nonzero in-degree and a cycle exists.

The insight that a cycle in a dependency graph means the task set can never complete is obvious once stated and the source of real bugs in real systems. Also satisfying to explain in interviews, because it sounds profound and is also just correct.


Dynamic Programming

15. Climbing Stairs (LeetCode 70)

The Fibonacci recurrence as a DP problem. To reach step n, you came from step n-1 or n-2.

Canonical not because it's hard but because it introduces the core DP template: define the state, write the recurrence, identify the base cases. Every 1D DP problem follows this structure. This is where you learn to write it without thinking. It is the "Hello World" of dynamic programming. Do not skip it because it feels easy.

16. Coin Change (LeetCode 322)

Find the minimum number of coins to make a target amount. An unbounded knapsack variant: each coin can be used unlimited times.

The DP framework is clean. dp[i] = minimum coins to make amount i. For each amount, try every coin: dp[i] = min(dp[i], dp[i - coin] + 1). Ascending iteration ensures every subproblem is solved before you need it.

Initialize dp to infinity (except dp[0] = 0). This is one of those details that is obvious after you get it wrong.

17. Word Break (LeetCode 139)

Can the string be segmented into dictionary words? dp[i] is true if the substring ending at position i can be built from dictionary words.

This introduces DP over string prefixes, a pattern that appears in Palindrome Partitioning, Decode Ways, and every "how many ways to parse this" problem. If you understood Coin Change, this feels structurally familiar. That's intentional. DP problems are the same ten ideas in different clothes.


Design Problems

18. LRU Cache (LeetCode 146)

Get and put, both O(1). A hash map for O(1) lookup. A doubly linked list for O(1) order updates.

The combination of two data structures to meet two independent constraints is the central design lesson. Every system where you need "find fast and evict oldest" uses this principle. The implementation forces you to work with sentinel nodes to simplify edge cases. Using sentinel nodes feels like cheating until you realize it eliminates an entire category of null-pointer bugs.

Full implementation breakdown here.

19. Find Median from Data Stream (LeetCode 295)

Two heaps: a max-heap for the lower half, a min-heap for the upper half. Keep them balanced within one element. The median is the top of one heap or the average of both.

This introduces the two-heap pattern for running order statistics, which shows up in Sliding Window Median and every problem tracking a dynamically changing dataset. "Use two heaps" sounds like a magic trick the first time. It stops being a trick once you understand that you're just maintaining a sorted invariant across two boundaries.

20. Serialize and Deserialize Binary Tree (LeetCode 297)

Encode a tree to a string. Decode it back. Preorder traversal with explicit null markers does both with a clean recursive structure.

The capstone: traversal order, recursion, and string parsing all at once. The general principle: any recursive structure can be serialized by encoding the recursive calls explicitly. JSON, XML, and abstract syntax trees all work this way. You've been using this algorithm every time you call JSON.stringify. You just didn't know its name.


How to Practice These Classic Coding Interview Problems

Don't read it linearly and call it done. Reading solutions and nodding along is not preparation. It's the illusion of preparation, which is actively worse because it makes you feel ready.

The goal is fluency: solving each problem cold, from scratch, with reasoning out loud.

Write the brute force first and name what it costs. Derive the better solution from first principles. Identify the invariant that makes it correct. Then solve a variant cold to confirm you understood the pattern, not just the solution.

These twenty should be automatic before your first real interview. The Top 50 LeetCode Mediums is the natural next step once they are.

If you want to verify that fluency is real and not just recognition, practice talking through each one out loud. Most people discover they can solve problems silently that they completely fall apart on when asked to explain. SpaceComplexity runs timed mock interviews that score your communication and reasoning alongside your code. The gap between "I know this" and "I can demonstrate this under pressure" is bigger than most people expect.


Further Reading