Top 15 LeetCode Problems for Frontend Engineer Interviews
- DOM tree problems dominate frontend onsite rounds because every interviewer knows the virtual DOM is a tree. Spend at least a third of your prep here.
- LCA (LC 236) is the hardest tree problem on the list. The key insight: a node receiving non-null from both subtrees is the lowest common ancestor.
- Flatten Nested Array (LC 2625) is the most frontend-specific problem. The depth parameter is the trap most candidates miss on edge cases.
- LRU Cache (LC 146) separates mid-level from senior candidates. Practice the hash map plus doubly linked list implementation from memory at least once.
- Course Schedule (LC 207) maps directly to module bundler dependency resolution. A cycle in the dependency graph means the build cannot complete.
- Narrating while coding is a separate skill from solving. Grinding problems in silence won't train the dimension interviewers actually score.
You're prepping for a frontend role. You search "LeetCode problems for frontend interviews," land on Blind 75, and an hour later you're elbow-deep in network flow. You wonder if any of this maps to your actual job, where the hardest thing you did last week was wrangle a CSS cascade.
Some of it does. A lot of it doesn't. Frontend interviews test a narrower DSA slice than general SWE loops. Trees dominate because the DOM is a tree. Recursion over nested structures is practically expected. Hard dynamic programming and advanced graph algorithms rarely appear. Grinding the wrong problems wastes weeks.
Here are the 15 problems that actually show up, grouped by the pattern they test.
15 LeetCode Problems for Frontend Interviews at a Glance
| # | Problem | Pattern | LC | Difficulty |
|---|---|---|---|---|
| 1 | Two Sum | Hash Map | 1 | Easy |
| 2 | Group Anagrams | Hash Map + Sort | 49 | Medium |
| 3 | Longest Substring Without Repeating Characters | Sliding Window | 3 | Medium |
| 4 | Valid Parentheses | Stack | 20 | Easy |
| 5 | Maximum Depth of Binary Tree | Tree DFS | 104 | Easy |
| 6 | Invert Binary Tree | Tree DFS | 226 | Easy |
| 7 | Lowest Common Ancestor of a Binary Tree | Tree DFS | 236 | Medium |
| 8 | Binary Tree Level Order Traversal | Tree BFS | 102 | Medium |
| 9 | Flatten Deeply Nested Array | Recursion | 2625 | Medium |
| 10 | Number of Islands | Graph BFS/DFS | 200 | Medium |
| 11 | Course Schedule | Topological Sort | 207 | Medium |
| 12 | Subarray Sum Equals K | Prefix Sum + Hash Map | 560 | Medium |
| 13 | Merge Intervals | Intervals | 56 | Medium |
| 14 | Climbing Stairs | 1D DP | 70 | Easy |
| 15 | LRU Cache | Design | 146 | Medium |
The DOM Is a Tree. No, Literally.
You know what else is a tree? The DOM. React's reconciler. The virtual DOM. JSX before it gets compiled. The component hierarchy you stare at in DevTools. Every tree traversal problem is a stealth DOM question, and interviewers at Meta, Google, and Airbnb know this.
They are not reaching for tree problems by accident. They are reaching for them because you work with trees every single day and they want to see if you know it yet.
Spend a third of your prep here. If that sounds like a lot, remember that Homebrew's creator famously failed to invert a binary tree in a Google interview. He went on to create one of the most widely used developer tools in history. The tree still got him.
The DOM you've been working with all along. Just a tree. Always has been.
Hash Map: Know Where You've Been
Two Sum (LC 1) tests whether you reach for a complement lookup instead of a nested loop. The interviewer wants to see you declare the map, compute target - nums[i], check membership, and store the index. All in one pass.
function twoSum(nums: number[], target: number): number[] { const seen = new Map<number, number>(); for (let i = 0; i < nums.length; i++) { const complement = target - nums[i]; if (seen.has(complement)) return [seen.get(complement)!, i]; seen.set(nums[i], i); } return []; }
Group Anagrams (LC 49) is the normalized-key variant. Sort each string to get a canonical key, then group strings with the same key. Two anagrams produce identical sorted strings. This pattern shows up in frontend constantly, any time you group data by a computed property.
If you ever feel like every problem reduces to a hash map, that's because a suspicious number of them do.
Sliding Window: One Variable Eliminates the Nested Loop
Longest Substring Without Repeating Characters (LC 3) is the textbook sliding window problem and one of the most consistently reported medium-difficulty questions across frontend onsite loops.
function lengthOfLongestSubstring(s: string): number { const seen = new Map<string, number>(); let left = 0, max = 0; for (let right = 0; right < s.length; right++) { if (seen.has(s[right])) left = Math.max(left, seen.get(s[right])! + 1); seen.set(s[right], right); max = Math.max(max, right - left + 1); } return max; }
The subtle bug most candidates write: updating left without the Math.max guard. If a character appeared before the current window, you don't want to jump left backward. The Math.max(left, ...) prevents that. It's one line. It will cost you the question if you miss it.
For more sliding window variants, the sliding window algorithm guide covers the full pattern with worked examples.
Stack: Every Parser Is a Stack
Valid Parentheses (LC 20) shows up constantly, and the reason goes deeper than "it's easy." Parsers are stacks. JSX gets parsed, HTML gets parsed, template literals get parsed. When this comes up in a frontend interview, the interviewer is checking whether stack intuition is natural to you, not just whether you can match brackets.
function isValid(s: string): boolean { const stack: string[] = []; const pairs: Record<string, string> = { ')': '(', ']': '[', '}': '{' }; for (const c of s) { if ('([{'.includes(c)) { stack.push(c); } else if (stack.pop() !== pairs[c]) return false; } return stack.length === 0; }
One trap: candidates pop before checking if the stack is empty. stack.pop() returns undefined when empty, and undefined !== pairs[c] is true, so the early return fires correctly. Know why that works. Your interviewer definitely will.
Trees: This Is Where Frontend Interviews Live
Expect at least one tree problem in most onsite loops. Often two. This is the section where you earn your offer or your polite rejection email.
Maximum Depth of Binary Tree (LC 104) is the warm-up: return 1 + Math.max(maxDepth(node.left), maxDepth(node.right)). Solve it in under two minutes. The real tree problem is coming.
Invert Binary Tree (LC 226) is the next step. Swap left and right at each node recursively. You are performing a single transform at every node, and recursion handles the traversal for free. Also this is the one Homebrew guy couldn't solve at Google. Practice it anyway.
Lowest Common Ancestor of a Binary Tree (LC 236) is where things get interesting. The key insight: any node receiving a non-null result from both recursive calls is the LCA. You bubble up p or q when you find them, and the meeting point identifies itself.
function lowestCommonAncestor( root: TreeNode | null, p: TreeNode, q: TreeNode ): TreeNode | null { if (!root || root === p || root === q) return root; const left = lowestCommonAncestor(root.left, p, q); const right = lowestCommonAncestor(root.right, p, q); return left && right ? root : left ?? right; }
The left && right check is the whole algorithm. Both sides return something means you're at the ancestor. Only one side means bubble it up.
Tree BFS: Process the DOM Level by Level
Binary Tree Level Order Traversal (LC 102) is the BFS counterpart to DFS tree problems. The trick is the inner snapshot loop: record queue.length before each level, iterate exactly that many times, then whatever remains belongs to the next level.
function levelOrder(root: TreeNode | null): number[][] { if (!root) return []; const result: number[][] = []; const queue: TreeNode[] = [root]; while (queue.length) { const level: number[] = []; for (let i = 0, len = queue.length; i < len; i++) { const node = queue.shift()!; level.push(node.val); if (node.left) queue.push(node.left); if (node.right) queue.push(node.right); } result.push(level); } return result; }
Rendering engines process DOM nodes top-down by level. React's reconciler traverses component trees in this order. This is not a contrived pattern from a whiteboard. It is your actual job, abstracted one level up.
For a deeper comparison of when to reach for each traversal, see the BFS vs DFS guide.
Recursion: Flatten Everything
Flatten Deeply Nested Array (LC 2625) is the most frontend-specific problem on this list. Meta has asked variants in phone screens. Given a nested array and a depth limit, return the flattened version.
function flat(arr: any[], n: number): any[] { if (n === 0) return arr.slice(); const result: any[] = []; for (const item of arr) { if (Array.isArray(item) && n > 0) { result.push(...flat(item, n - 1)); } else { result.push(item); } } return result; }
The depth parameter is the trap. Candidates who flatten recursively without tracking depth pass every basic test and then fail n=1 so cleanly it hurts to watch. Variants include flattening nested objects, which uses the same recursion with a typeof item === 'object' check instead of Array.isArray. You've done this during JSON normalization and never thought about it algorithmically. Now you have to.
Graph: Count the Connected Regions
Number of Islands (LC 200) shows up across all interview types, frontend included. Treat the grid as a graph. When you find unvisited land, run DFS or BFS to mark the entire connected region, then increment your counter.
The outer loop over all cells is not optional. Disconnected islands don't get traversed otherwise. That one missing loop has cost more candidates this problem than any logic error inside the traversal.
Why this matters for frontend: flood-fill tools, interactive map regions, canvas drawing zones. All connected-component problems wearing a different outfit.
Topological Sort: Your Bundler Does This Every Day
Course Schedule (LC 207) is more relevant than it sounds. Module bundlers resolve import dependencies in topological order. npm resolves package dependencies the same way. A cycle in the dependency graph means resolution is impossible, and this problem detects exactly that. You've seen the error. Now you understand what's detecting it.
Use Kahn's algorithm: build in-degree counts for every node, seed a queue with zero-in-degree nodes, process them one by one while decrementing neighbors, and enqueue any neighbor that reaches zero. If the number of processed nodes equals the total, there's no cycle.
Prefix Sum: The Invisible Accumulation
Subarray Sum Equals K (LC 560) combines a running prefix sum with a hash map. At each index, you know the total sum from index 0. If prefixSum - k exists in the map, a subarray summing to k ends at the current index.
The non-obvious step: initialize the map with {0: 1} before iterating. This handles the case where the subarray starts at index 0. Miss this and you fail every test case where the target sum starts at the beginning. Interviewers have seen candidates debug this for ten minutes before realizing they forgot one line.
Intervals and Light DP
Merge Intervals (LC 56): sort by start time. If the next interval's start is at or before the current end, merge by taking the max of the two ends. The Math.max call is not optional, because a contained interval would otherwise truncate the merged result. This bug is silent. It passes most tests. It fails the interviewer's first edge case.
Climbing Stairs (LC 70): the entry-level DP problem. dp[i] = dp[i-1] + dp[i-2]. Fibonacci with different names. Once this pattern clicks, any 1D DP variant a frontend interviewer throws at you is a variation on the same state machine. It is also, genuinely, the only DP you need for most frontend loops.
LRU Cache: The Senior-Level Checkpoint
LRU Cache (LC 146) tests whether you can combine a hash map and doubly linked list to get O(1) get and O(1) put. Browser history, recently viewed items, memoization wrappers. All LRU-adjacent. This is the question that separates mid-level from senior candidates in frontend loops, and the gap is usually not algorithmic knowledge. It's whether you can build two data structures and wire them together cleanly under pressure.
Practice it from memory at least once. The full implementation with edge cases is covered in the LRU Cache implementation guide.
The Constraint You're Not Practicing
You can solve all 15 of these problems alone in silence and still fail an interview. LeetCode does not care if you go quiet for four minutes, forget to state your complexity before coding, or start writing without clarifying the input. Your interviewer cares a lot.
Narrating your approach while you build is a separate skill, and it requires reps where someone is actually listening. Solving a problem correctly and explaining your thinking out loud while you solve it are two different activities. Most people practice the first one exclusively.
If you want reps on the second, SpaceComplexity runs voice-based DSA interviews with rubric scoring across communication, problem-solving, code quality, and edge case handling. The feedback targets the dimension most candidates skip entirely.
For a broader look at what frontend engineers get tested on across all rounds, see the DSA for frontend engineers guide.
Further Reading
- Front End Interview Handbook: Algorithms, concise breakdown of what to expect and what to skip
- GreatFrontend: Cracking DSA, curated problem set with JavaScript solutions
- LeetCode Top Interview Questions: Trees and Graphs, the canonical medium-difficulty tree set