DSA Interview Guide 2026 — 14 Patterns, LeetCode Strategy, and a 90-Day Plan
Advertisement
Introduction
Why This Matters
Grinding 500 LeetCode problems randomly is one of the least efficient ways to prepare for coding interviews. Top engineers who crack FAANG rounds typically recognize patterns — once they see a problem, they map it to a known pattern and apply a template. This guide teaches 14 patterns that cover roughly 90% of interview problems, alongside a structured plan to build that recognition quickly.
The gap between engineers who get offers and those who do not is rarely raw intelligence. It is the ability to recognize what kind of problem you are looking at within 60 seconds, and to communicate your approach clearly while coding.
The 14 Core Patterns
1. Two Pointers — Pair sums, palindromes, three-sum
2. Sliding Window — Longest/shortest subarray with constraint X
3. Fast and Slow Pointers — Cycle detection, middle of linked list
4. Binary Search — Sorted/rotated arrays, search space problems
5. Merge Intervals — Overlapping, non-overlapping intervals
6. Cyclic Sort — Arrays with values in range 1 to N
7. Tree BFS — Level order, shortest path in trees
8. Tree DFS — Path sum, subtree match, serialization
9. Two Heaps — Median of data stream, scheduling
10. Subsets — Combinations, permutations, power set
11. Modified Binary Search — Find in rotated array, bitonic search
12. Top K Elements — K largest, K most frequent, closest points
13. K-way Merge — Merge K sorted lists/arrays
14. Dynamic Programming — Optimization, counting, feasibilityPattern 1 — Two Pointers
When to use: sorted array, finding pairs with a target sum, in-place operations, or removing duplicates.
// Two sum in sorted array — O(n) time, O(1) space
function twoSum(nums: number[], target: number): number[] {
let left = 0, right = nums.length - 1
while (left < right) {
const sum = nums[left] + nums[right]
if (sum === target) return [left, right]
if (sum < target) left++
else right--
}
return []
}
// 3Sum — O(n²) time
function threeSum(nums: number[]): number[][] {
nums.sort((a, b) => a - b)
const result: number[][] = []
for (let i = 0; i < nums.length - 2; i++) {
if (i > 0 && nums[i] === nums[i - 1]) continue
let left = i + 1, right = nums.length - 1
while (left < right) {
const sum = nums[i] + nums[left] + nums[right]
if (sum === 0) {
result.push([nums[i], nums[left], nums[right]])
while (left < right && nums[left] === nums[left + 1]) left++
while (left < right && nums[right] === nums[right - 1]) right--
left++; right--
} else if (sum < 0) left++
else right--
}
}
return result
}Pattern 2 — Sliding Window
When to use: "longest / shortest subarray satisfying condition X", "count subarrays with property Y". The window expands on the right and contracts on the left.
// Longest substring without repeating characters
function lengthOfLongestSubstring(s: string): number {
const seen = new Map<string, number>()
let maxLen = 0, left = 0
for (let right = 0; right < s.length; right++) {
const char = s[right]
if (seen.has(char) && seen.get(char)! >= left) {
left = seen.get(char)! + 1
}
seen.set(char, right)
maxLen = Math.max(maxLen, right - left + 1)
}
return maxLen
}Template: expand right pointer, check constraint, shrink left pointer while constraint violated, update answer.
Pattern 3 — Binary Search
When to use: sorted or rotated array, any problem where the search space can be halved, "find minimum/maximum satisfying condition".
// Search in rotated sorted array
function searchRotated(nums: number[], target: number): number {
let left = 0, right = nums.length - 1
while (left <= right) {
const mid = left + Math.floor((right - left) / 2)
if (nums[mid] === target) return mid
// Left half is sorted
if (nums[left] <= nums[mid]) {
if (nums[left] <= target && target < nums[mid]) right = mid - 1
else left = mid + 1
} else {
// Right half is sorted
if (nums[mid] < target && target <= nums[right]) left = mid + 1
else right = mid - 1
}
}
return -1
}Key insight: use left + Math.floor((right - left) / 2) instead of (left + right) / 2 to avoid integer overflow.
Pattern 4 — Tree Traversals
// DFS — all root-to-leaf paths
function allPaths(root: TreeNode | null): number[][] {
const paths: number[][] = []
function dfs(node: TreeNode | null, path: number[]) {
if (!node) return
path.push(node.val)
if (!node.left && !node.right) paths.push([...path])
dfs(node.left, path)
dfs(node.right, path)
path.pop() // backtrack
}
dfs(root, [])
return paths
}
// BFS — level order traversal
function levelOrder(root: TreeNode | null): number[][] {
if (!root) return []
const result: number[][] = []
const queue = [root]
while (queue.length) {
const size = queue.length
const level: number[] = []
for (let i = 0; i < size; 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
}Rule of thumb: BFS for shortest path or level-by-level processing. DFS for path problems, subtree matching, and serialization.
Pattern 5 — Graph Traversal
// BFS for shortest path in grid
function shortestPath(
grid: number[][],
start: [number, number],
end: [number, number]
): number {
const [rows, cols] = [grid.length, grid[0].length]
const dirs = [[0,1],[0,-1],[1,0],[-1,0]]
const visited = new Set<string>([start.toString()])
const queue: [[number, number], number][] = [[start, 0]]
while (queue.length) {
const [[row, col], dist] = queue.shift()!
if (row === end[0] && col === end[1]) return dist
for (const [dr, dc] of dirs) {
const [nr, nc] = [row + dr, col + dc]
const key = `${nr},${nc}`
if (nr >= 0 && nr < rows && nc >= 0 && nc < cols
&& grid[nr][nc] === 0 && !visited.has(key)) {
visited.add(key)
queue.push([[nr, nc], dist + 1])
}
}
}
return -1
}Graph interview checklist: directed or undirected? Weighted or unweighted? Cyclic? Connected? Answer these before choosing BFS, DFS, Dijkstra, or topological sort.
Pattern 6 — Dynamic Programming
Decision tree for DP:
- Does the problem ask for min, max, count, or feasibility? → candidate for DP
- Does it have overlapping subproblems? → memoize or tabulate
- Does it have optimal substructure? → DP over greedy
// Coin change — fewest coins to make amount
function coinChange(coins: number[], amount: number): number {
const dp = Array(amount + 1).fill(Infinity)
dp[0] = 0
for (let i = 1; i <= amount; i++) {
for (const coin of coins) {
if (coin <= i) dp[i] = Math.min(dp[i], dp[i - coin] + 1)
}
}
return dp[amount] === Infinity ? -1 : dp[amount]
}
// Longest common subsequence
function lcs(s1: string, s2: string): number {
const m = s1.length, n = s2.length
const dp = Array(m + 1).fill(null).map(() => Array(n + 1).fill(0))
for (let i = 1; i <= m; i++) {
for (let j = 1; j <= n; j++) {
if (s1[i-1] === s2[j-1]) dp[i][j] = dp[i-1][j-1] + 1
else dp[i][j] = Math.max(dp[i-1][j], dp[i][j-1])
}
}
return dp[m][n]
}The 90-Day Study Plan
| Phase | Days | Focus Areas | LeetCode Target |
|---|---|---|---|
| Foundations | 1–30 | Arrays, strings, linked lists, stacks, binary search | 50 problems (easy/medium) |
| Core Patterns | 31–60 | Trees, graphs, DP 1D and 2D, union-find | 80 problems (mostly medium) |
| Hard + Mocks | 61–90 | Hard DP, Dijkstra, tries, segment trees, mock interviews | 40 problems (medium/hard) |
Must-solve problems by topic:
Arrays: Two Sum, Best Time to Buy Stock, Product Except Self
Strings: Longest Substring, Group Anagrams, Valid Parentheses
Trees: Invert Tree, Max Depth, LCA, Serialize/Deserialize
Graphs: Number of Islands, Course Schedule, Word Ladder
DP: Climb Stairs, House Robber, Longest Increasing Subsequence
Intervals: Merge Intervals, Non-overlapping Intervals, Meeting RoomsMock interview resources: Pramp (free, peer-to-peer), Interviewing.io (anonymous with real engineers), NeetCode.io (pattern-organized video explanations).
Common Mistakes
- Skipping clarification. Before coding, ask: "Can the array be empty? Are there duplicates? Can values be negative?" These constraints change the solution.
- Optimizing prematurely. Start with a brute-force approach and state its complexity, then optimize. This shows structured thinking.
- Not talking while coding. Silence in a coding interview signals uncertainty. Narrate your reasoning even when it feels obvious.
- Ignoring edge cases. Empty input, single element, negative numbers, and integer overflow are the most common sources of test failures.
- Using the wrong data structure. Choosing an array when a HashMap reduces lookup from O(n) to O(1) is a common miss. Always ask: "What operation dominates? What structure optimizes it?"
Best Practices
- Write the function signature and a test case comment before touching the algorithm. It forces you to understand the I/O contract.
- Derive time and space complexity for every solution. Interviewers ask — have the answer ready before they do.
- After coding, walk through your solution with a concrete example (not the trivial case). Find your own bugs.
- For every problem you solve, ask: "Can I reduce space? Can I reduce time? What pattern does this belong to?"
- Spend at least 30 minutes per week on timed sessions — set a 25-minute timer per problem and submit what you have.
Key Takeaways
- Learning 14 patterns is more efficient than solving 500 random LeetCode problems because patterns transfer across problem families.
- Two Pointers reduces pair-finding problems from O(n²) to O(n) in sorted arrays by moving pointers inward based on the current sum.
- Sliding Window solves "longest/shortest subarray with constraint X" in O(n) by expanding and contracting a window rather than checking every subarray.
- Use
left + Math.floor((right - left) / 2)in binary search to avoid integer overflow that occurs with(left + right) / 2in some languages. - BFS guarantees the shortest path in an unweighted graph; DFS uses less memory for path enumeration and is preferred for subtree problems.
- Dynamic programming applies when a problem has both overlapping subproblems and optimal substructure — memoization (top-down) and tabulation (bottom-up) are equivalent in correctness.
- The 90-day study plan phases from foundations to patterns to hard problems mirrors how interviews escalate in difficulty across rounds at the same company.
- Communicating your reasoning while coding is as important as producing a correct solution — interviewers evaluate thinking process, not just output.
Advertisement