20 LeetCode Problems That Teach Edge-Case Thinking

- Initialize to
nums[0], not0: Maximum Subarray and similar problems return wrong results on all-negative inputs when the neutral value implies an empty subarray is valid. - Integer overflow in binary search:
(lo+hi)//2wraps in Java and C++; uselo + (hi-lo)//2to eliminate it. - Track both min and max in product subarrays: A negative times a negative flips the minimum into the new maximum, so carrying only the running max misses valid answers.
- Read the constraint block before coding: Find the Duplicate Number hides the O(1)-space algorithm inside the
[1,n]constraint, not the problem statement. - The contained-interval bug in Merge Intervals: Using
interval[1]instead ofmax(current_end, interval[1])shrinks merges and silently fails on fully nested intervals. k %= nis the first line in rotation problems: Any k ≥ n reduces to a smaller shift; skipping this causes wrong results when k equals the array length.- Three edge case families: Most LeetCode failures are neutral-value traps, constraint-driven special cases, or off-by-one and overflow errors.
You solved it. The algorithm is correct. The complexity checks out, you talked through your approach, the interviewer gave a small nod. You run it on their test case. It returns 0. The right answer is -3.
You initialized your running maximum to zero. Again.
The algorithm gets you 80% of the way. Edge cases are the other 20%. And unlike the algorithm, edge-case thinking is trainable. Certain problems teach it better than others because the naive solution almost works and the failure mode reveals something transferable.
Here are the 20 that do it most efficiently.
The Full List at a Glance
| # | Problem | LeetCode | The Edge Case That Gets You |
|---|---|---|---|
| 1 | Maximum Subarray | 53 | All-negative array: init to nums[0], never 0 |
| 2 | Binary Search | 704 | (lo+hi)//2 overflows; loop bound decides if last element is checked |
| 3 | Valid Parentheses | 20 | Stack must be empty at end, not just during processing |
| 4 | Rotate Array | 189 | k can exceed n; always k %= n first |
| 5 | Reverse Integer | 7 | Check 32-bit bounds before appending each digit |
| 6 | 3Sum | 15 | Skip duplicates after finding each triplet or you get duplicate results |
| 7 | Decode Ways | 91 | "0" alone is invalid; "30" exceeds 26 and kills the preceding digit |
| 8 | Maximum Product Subarray | 152 | Track both min and max; a negative times a negative turns positive |
| 9 | Merge Intervals | 56 | Contained intervals: end must be max(current_end, next_end), not next_end |
| 10 | Trapping Rain Water | 42 | Arrays shorter than 3 return 0; monotone arrays trap nothing |
| 11 | Find the Duplicate Number | 287 | The [1,n] constraint is the whole trick; values are valid indices |
| 12 | Product of Array Except Self | 238 | Two zeros make every element zero; one zero makes all non-zero positions zero |
| 13 | Valid Palindrome | 125 | Empty string after filtering is a palindrome; comparison must be case-insensitive |
| 14 | Jump Game | 55 | Single element is always reachable; last element needs no jump value at all |
| 15 | Search in Rotated Sorted Array | 33 | No rotation is a valid case; handle when nums[lo] <= nums[mid] covers both halves |
| 16 | Minimum Window Substring | 76 | len(s) < len(t) has no window; target empty string is technically valid |
| 17 | Container With Most Water | 11 | Width is right - left, never right - left + 1; easy to confuse with 1-indexed counting |
| 18 | Serialize/Deserialize Binary Tree | 297 | A null tree serializes to a non-empty string; skewed trees expand token count dramatically |
| 19 | Course Schedule | 207 | Self-loop at any node is an immediate cycle; zero courses is always schedulable |
| 20 | Palindrome Number | 9 | Negative numbers are never palindromes; any number ending in 0 (except 0 itself) is not |
Seven That Bite Smart People Repeatedly
These seven have failure modes subtle enough to show up in senior engineer interviews. The rest you can absorb through practice. Start here.
Maximum Subarray: The Init Trap Will Find You
Initialize max_current = nums[0], never 0. Starting at zero implicitly allows an empty subarray. The problem requires at least one element. An all-negative array should return its least-negative value, not zero, and initializing to zero is you lying to your own algorithm about what the input looks like.
def max_subarray(nums: list[int]) -> int: max_current = max_global = nums[0] for num in nums[1:]: max_current = max(num, max_current + num) max_global = max(max_global, max_current) return max_global
The lesson generalizes: whenever you initialize a running optimum, ask what happens when every element is negative. A neutral starting value often hides an assumption about the data. That assumption will be wrong on test case 23.
Binary Search: Two Independent Bugs in One Function
Two separate edge cases, same function. First, the integer overflow bug that lived in Java's standard library for nine years: mid = (lo + hi) // 2 wraps when lo + hi exceeds the integer maximum. Fix it with lo + (hi - lo) // 2. Python integers are arbitrary-precision, so this is a Java and C++ problem specifically. The Java fix shipped in 2006. The bug was introduced in 1986.
Second, the termination condition. while lo < hi and while lo <= hi are not interchangeable. Each requires a different placement of lo = mid + 1 vs lo = mid. Pick one invariant and never mix them. The binary search off-by-one error covers both forms in detail.
Decode Ways: Zeros Will Kill You
Decode Ways (LC 91) is the canonical edge-case DP problem. The string "0" alone has zero decodings. "30" also has zero decodings because 30 exceeds 26. The trap is "10" and "20": you can decode the pair together (J and T), but the trailing "0" has no single-digit decoding. So dp[i] cannot add dp[i-1] when s[i] == '0'.
def num_decodings(s: str) -> int: if not s or s[0] == '0': return 0 dp = [0] * (len(s) + 1) dp[0] = 1 dp[1] = 1 for i in range(2, len(s) + 1): one_digit = int(s[i-1]) two_digits = int(s[i-2:i]) if one_digit != 0: dp[i] += dp[i-1] if 10 <= two_digits <= 26: dp[i] += dp[i-2] return dp[-1]
Every value constraint maps to a guard in your DP transitions. List the constraints explicitly before writing a single line of state logic. Skipping this step is how you spend 20 minutes debugging a solution that was always going to be wrong.
Maximum Product Subarray: When Tracking the Maximum Isn't Enough
Tracking only the maximum running product fails. A large negative product multiplied by another negative number becomes the array maximum. You have to track both the current minimum and current maximum at every step, then swap them when the next number is negative.
This generalizes to any operation that can flip ordering: negation, division by a negative, modular arithmetic. Carry both extremes. Ignoring the minimum because "we want the maximum" is the bug.
Merge Intervals: The Contained Interval Destroys Your Merge
Given [1,10] and [2,3], the merged result is [1,10]. Write merged[-1][1] = interval[1] instead of merged[-1][1] = max(merged[-1][1], interval[1]) and the contained interval shrinks your merge down to [1,3]. The smaller interval ate the larger one.
Always take the max of both endpoints. Most test cases show intervals that extend each other, not intervals nested inside another. This bug passes all your informal tests and fails in the judge on case 47.
Find the Duplicate Number: It Was in the Constraints the Whole Time
The problem looks like it wants a hash set. O(n) time, O(n) space, done. Except the constraints say the array has n+1 integers each in [1, n], meaning every value is a valid index. That turns the array into a linked list where nums[i] points to the next node, and the duplicate creates a cycle. Floyd's cycle detection solves it in O(n) time and O(1) space.
The constraint [1, n] is the algorithm. Reading the constraint block is design work, not background reading you skim past to get to the fun part.
Rotate Array: One Mod Before Everything Else
k = 6 on an array of length 3 is k = 0. Write k %= len(nums) as the first line. If k becomes 0, return early.
Simple once you see it. Easy to miss when you read "rotate by k steps" and k feels like a forward shift count. Six steps on a three-element array is just standing still.
Most LeetCode Edge Cases Fall Into Three Families
Across all 20 problems, the failures cluster into three categories. Recognizing which family you're in is most of the fix.
Neutral value traps. You initialize something to 0, True, or an empty string when the correct neutral depends on the data. Maximum Subarray, Maximum Product Subarray, and Valid Palindrome all do this. The question to ask: "Does my initial value make a silent assumption about the input?" If the answer involves the word "probably," you have a trap.
Constraint-driven special cases. The problem hides something in the constraint block that invalidates the naive approach entirely. Find the Duplicate Number uses [1, n]. Decode Ways uses [1, 26] for valid codes. Rotate Array uses k mod n. Constraints are algorithm design. Reading them as fine print is how you get surprised.
Off-by-one and overflow. Binary Search and Reverse Integer are the canonical cases. Container With Most Water (width is right - left, not right - left + 1) is close behind. The integer overflow article covers the Java-specific version in depth.
The useful thing about families: once you recognize the type, you know the fix before you know the bug. Seeing a running optimum means asking about neutral values. Seeing a value range in the constraints means asking whether it changes the data structure. Seeing an index calculation means asking whether your bounds match your loop invariant.
The Five-Minute Habit That Builds This Skill
After you reach a correct solution, spend five minutes on the edge cases checklist:
- What if the input is empty?
- What if it has one element?
- What if all elements are equal?
- What if all elements are negative?
- What if the answer is
0,false, ornull? - What if a value is at or near the integer limits?
Do it every time, not just on problems that look tricky. Pattern recognition transfers. You stop getting surprised.
The goal isn't to memorize 20 failure modes. It's to build the reflex of asking the question. Most interviewers aren't waiting for you to produce a perfect solution. They're watching whether you think to check the thing that will break it.
If you want to practice catching these under pressure, SpaceComplexity runs voice interviews where the interviewer follows up specifically on edge cases, then gives rubric-based feedback on whether you caught them before being prompted. It's faster feedback than a failed submission and closer to the real thing than running test cases in silence.
Further Reading
- LeetCode problem 53: Maximum Subarray, read the discussion tab for the all-negative edge case specifically
- LeetCode problem 91: Decode Ways, top-voted solutions all handle zeros differently; comparing them is instructive
- Integer overflow in Java's binary search, Joshua Bloch (Google Blog, 2006)
- GeeksforGeeks: Edge cases in binary search
- Wikipedia: Off-by-one error
- Wikipedia: Integer overflow