Algorithms and Data Structures for Software Engineers (2026)
In short
Algorithm interviews in 2026 reward pattern recognition, not problem volume. The candidates who clear FAANG-tier coding rounds have ~150 problems deep — not 500 shallow. This page walks the eight patterns that cover ~80% of senior+ coding interviews, with annotated Python code and the named LeetCode problems where each pattern is the canonical solution. Source structure follows NeetCode's 150 categorization (neetcode.io/roadmap) with worked examples that explain when each pattern is the right tool.
Key takeaways
- ~80% of senior+ coding rounds map to 8 patterns: two pointers, sliding window, fast/slow pointers, BFS, DFS, backtracking, dynamic programming, and monotonic stack/deque (per NeetCode 150 categorization, neetcode.io/roadmap).
- BFS finds shortest paths in unweighted graphs and explores level-by-level; DFS explores deeply and is correct for cycle detection, topological sort, and connectivity. Confusing them costs the round.
- Sliding window collapses a nested-loop O(n^2) into O(n) when the inner loop only ever shrinks or grows monotonically — LeetCode 76 'Minimum Window Substring' is the canonical hard problem.
- Dynamic programming is recognition (overlapping subproblems + optimal substructure), then formulation (state + transition), then implementation (memo or tabulation). Most candidates fail at recognition. LeetCode 198 'House Robber' is the simplest 1D DP teach-the-pattern problem.
- Time complexity questions are graded on the worst case and the explanation, not the symbol — say 'O(V+E) because BFS visits each vertex once and each edge twice in adjacency list', not just 'O(V+E)'.
- Mock interviews close the gap between solving alone and solving under pressure — Hello Interview's mock pool and interviewing.io give realistic feedback; aim for 8+ before applying to FAANG-tier.
Pattern 1: Two pointers (when both ends move toward each other)
Two pointers is the simplest non-trivial pattern and the highest-leverage one to internalize first. Recognition trigger: a sorted array (or one you can sort), and the answer involves comparing two elements.
Canonical problem: LeetCode 167 'Two Sum II — Input Array Is Sorted'.
def two_sum_sorted(nums: list[int], target: int) -> list[int]:
"""
Return 1-indexed positions of two numbers summing to target.
O(n) time, O(1) space — strictly better than the hashmap O(n) space approach.
"""
left, right = 0, len(nums) - 1
while left < right:
s = nums[left] + nums[right]
if s == target:
return [left + 1, right + 1]
elif s < target:
left += 1 # need bigger sum; advance the small side
else:
right -= 1 # need smaller sum; retreat the big side
return [] # never hit on valid inputWhen two pointers is wrong: when the array isn't sorted and the cost of sorting (O(n log n)) exceeds the cost of a hashmap solution (O(n) time, O(n) space). LeetCode 1 'Two Sum' on an unsorted array — use the hashmap, not two pointers.
Other LeetCode problems where two pointers is canonical:
- 15 — 3Sum (sort first, then two pointers inside a loop, O(n^2)).
- 11 — Container With Most Water (the trick: move the shorter pointer; the taller one can never improve from this side).
- 42 — Trapping Rain Water (two pointers + tracking max-so-far on each side).
- 125 — Valid Palindrome (two pointers on string with skip-non-alpha).
Pattern 2: Sliding window (variable-length subarray with a constraint)
Sliding window converts O(n^2) brute-force scans into O(n) by maintaining a window state and shrinking from one side as it expands from the other. Recognition trigger: 'longest/shortest/sum-of subarray such that...'
Canonical problem: LeetCode 3 'Longest Substring Without Repeating Characters'.
def longest_unique(s: str) -> int:
seen = {} # char -> last seen index
left = 0
best = 0
for right, ch in enumerate(s):
if ch in seen and seen[ch] >= left:
# shrink window: jump left to past the duplicate
left = seen[ch] + 1
seen[ch] = right
best = max(best, right - left + 1)
return best
# Time: O(n) — each index is visited at most twice (once by right, once by left).
# Space: O(min(n, alphabet_size))The hard problem candidates miss: LeetCode 76 'Minimum Window Substring'. Track required chars with a counter; expand right until window covers all; then shrink left while still valid; record minimum. The canonical pattern of 'expand to satisfy, shrink to optimize'.
from collections import Counter
def min_window(s: str, t: str) -> str:
if not s or not t:
return ""
need = Counter(t)
have = Counter()
needed_chars = len(need)
have_chars = 0
left = 0
best = (float('inf'), 0, 0) # length, l, r
for right, ch in enumerate(s):
have[ch] += 1
if ch in need and have[ch] == need[ch]:
have_chars += 1
while have_chars == needed_chars:
if right - left + 1 < best[0]:
best = (right - left + 1, left, right)
have[s[left]] -= 1
if s[left] in need and have[s[left]] < need[s[left]]:
have_chars -= 1
left += 1
return s[best[1]:best[2] + 1] if best[0] < float('inf') else ""Other LeetCode problems for the pattern: 209 (Minimum Size Subarray Sum), 424 (Longest Repeating Character Replacement), 567 (Permutation in String).
Pattern 3: BFS vs DFS — when to use which
This is the single most-failed concept in graph interviews. The wrong choice doesn't just cost time; it produces a wrong answer.
Use BFS when:
- You need the shortest path in an unweighted graph.
- You need to process nodes level by level (e.g., binary tree level order traversal — LeetCode 102).
- You need to find the minimum number of steps (LeetCode 127 'Word Ladder').
Use DFS when:
- You need to detect cycles in a directed graph (with three-coloring: unvisited, in-progress, finished).
- You need topological sort (DAGs — LeetCode 207 'Course Schedule', 210 'Course Schedule II').
- You need to explore all paths or generate combinations (backtracking is DFS).
- You're doing connected component detection where order doesn't matter (LeetCode 200 'Number of Islands').
BFS template (use this verbatim):
from collections import deque
def bfs_shortest_path(graph: dict, start, target):
"""Return the shortest path length from start to target, or -1."""
if start == target:
return 0
visited = {start}
queue = deque([(start, 0)]) # (node, distance)
while queue:
node, dist = queue.popleft()
for neighbor in graph[node]:
if neighbor == target:
return dist + 1
if neighbor not in visited:
visited.add(neighbor)
queue.append((neighbor, dist + 1))
return -1
# Time: O(V + E). Space: O(V) for visited + queue.DFS iterative template (avoid recursion depth limits in Python):
def dfs_iter(graph: dict, start) -> set:
"""Return all nodes reachable from start."""
visited = set()
stack = [start]
while stack:
node = stack.pop()
if node in visited:
continue
visited.add(node)
for neighbor in graph[node]:
if neighbor not in visited:
stack.append(neighbor)
return visitedThe annotated 'Number of Islands' walkthrough (LeetCode 200, the canonical interview problem):
def num_islands(grid: list[list[str]]) -> int:
if not grid:
return 0
rows, cols = len(grid), len(grid[0])
count = 0
def dfs(r: int, c: int) -> None:
# Out-of-bounds or water? stop.
if r < 0 or r >= rows or c < 0 or c >= cols or grid[r][c] != '1':
return
# Mark this land cell visited (in-place mutation; ask the interviewer first).
grid[r][c] = '#'
dfs(r + 1, c); dfs(r - 1, c); dfs(r, c + 1); dfs(r, c - 1)
for r in range(rows):
for c in range(cols):
if grid[r][c] == '1':
count += 1
dfs(r, c)
return count
# Time: O(rows * cols). Space: O(rows * cols) recursion stack worst case.For very large grids, switch to iterative DFS or BFS — Python's recursion limit (~1000) will blow on 1000x1000 grids.
Pattern 4: Dynamic programming — recognition, formulation, implementation
DP is where most candidates lose senior+ rounds. Not because the problems are hard but because the recognition step is non-obvious. The recognition signal is two-part: overlapping subproblems and optimal substructure.
Teach-the-pattern problem: LeetCode 198 'House Robber'.
You are robbing houses on a street. You can't rob two adjacent houses. Maximize total loot.
Formulation (the load-bearing step):
# State: dp[i] = max money robbing houses 0..i
# Transition: dp[i] = max(dp[i-1], # skip house i
# dp[i-2] + nums[i]) # rob house i
# Base case: dp[0] = nums[0]; dp[1] = max(nums[0], nums[1])Tabulation implementation (preferred over memoization in interviews — easier to reason about complexity):
def rob(nums: list[int]) -> int:
if not nums:
return 0
if len(nums) == 1:
return nums[0]
# Space-optimized: only need the last two values.
prev2 = nums[0]
prev1 = max(nums[0], nums[1])
for i in range(2, len(nums)):
cur = max(prev1, prev2 + nums[i])
prev2, prev1 = prev1, cur
return prev1
# Time: O(n). Space: O(1) — note the optimization from O(n) to O(1).Mentioning the space optimization from O(n) array to O(1) two-variable rolling state is a standard senior-bar signal.
Common DP problem catalog (LeetCode numbers):
- 1D DP: 70 (Climbing Stairs), 198 (House Robber), 213 (House Robber II), 322 (Coin Change), 300 (Longest Increasing Subsequence).
- 2D DP / strings: 5 (Longest Palindromic Substring), 1143 (Longest Common Subsequence), 72 (Edit Distance).
- 2D DP / grids: 62 (Unique Paths), 64 (Minimum Path Sum), 221 (Maximal Square).
- Knapsack family: 416 (Partition Equal Subset Sum), 494 (Target Sum), 518 (Coin Change II).
The single book that teaches DP correctly: Algorithms by Sedgewick and Wayne (4th ed., chapter 6). Cracking the Coding Interview's DP coverage is shallow; CLRS is more theoretical than interviews require.
Pattern 5: Backtracking, monotonic stack, and the rest
Three more patterns that round out senior+ coverage.
Backtracking (DFS with state restoration). Recognition: combinatorial enumeration ('find all permutations / subsets / valid combinations'). Template:
def backtrack(state, choices):
if is_solution(state):
result.append(list(state)) # copy!
return
for choice in valid_choices(state, choices):
state.append(choice)
backtrack(state, choices)
state.pop() # the load-bearing step — restore stateCanonical: LeetCode 46 (Permutations), 78 (Subsets), 39 (Combination Sum), 51 (N-Queens).
Monotonic stack/deque. Recognition: 'next greater element' or 'sliding window maximum'.
# LeetCode 739 — Daily Temperatures: how many days until a warmer one?
def daily_temperatures(temps: list[int]) -> list[int]:
answer = [0] * len(temps)
stack = [] # indices, temps decreasing
for i, t in enumerate(temps):
while stack and temps[stack[-1]] < t:
j = stack.pop()
answer[j] = i - j
stack.append(i)
return answer
# Time: O(n) — each index pushed and popped at most once.Other monotonic-stack problems: 84 (Largest Rectangle in Histogram), 239 (Sliding Window Maximum — monotonic deque), 503 (Next Greater Element II).
Fast/slow pointers (Floyd's tortoise and hare). Recognition: cycle detection in linked lists, finding the middle.
# LeetCode 141 — Linked List Cycle
def has_cycle(head) -> bool:
slow = fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
if slow is fast:
return True
return FalsePlus: LeetCode 142 (find cycle start — clever math), 876 (middle of linked list).
Time and space complexity — how to talk about it
Stating complexity is grade-school; justifying complexity is the senior bar. The wrong answer is the symbol; the right answer is one sentence of why.
| Algorithm | Time | Space | Justification (say this out loud) |
|---|---|---|---|
| BFS / DFS on graph | O(V + E) | O(V) | Each vertex visited once; each edge inspected at most twice (adjacency list). |
| Sorting (Timsort, Python default) | O(n log n) | O(n) | Worst case is the merge phase; auxiliary array needed. |
| Sliding window | O(n) | O(window) | Each index touched by at most two pointers (left, right). |
| DP, 2D | O(rows * cols) | O(rows * cols) or O(min) | Each cell computed once; sometimes you can reduce to O(prev row only). |
| Backtracking | O(branching^depth) | O(depth) | State stack at most equal to depth; pruning reduces branching factor. |
| Heap / priority queue ops | O(log n) per op | O(n) | Heap ops are log because of the bubble-up/down on a balanced binary tree. |
Big mistakes candidates make:
- Calling a Python
list.appendO(1) amortized but not saying 'amortized' — interviewers notice. - Calling a hashmap lookup O(1) without saying 'expected; worst case O(n) on hash collisions'.
- Forgetting the recursion stack space in DFS — it's not free.
- Confusing 'this runs fast in Python' with 'this is asymptotically optimal' — Python's hashmap is slower than C's by a factor; the asymptotics are the same.
Prep cadence: 12 weeks to senior-bar coding fluency
Empirical pattern from candidates who clear FAANG senior+ rounds in 2026:
- Weeks 1-2 — Pattern 1-2 (two pointers, sliding window): 15-20 LeetCode problems mediums; 5 hards. Don't move on until the recognition triggers are automatic.
- Weeks 3-4 — Trees and graphs (BFS, DFS): 25 problems. Trees first (LC 102, 103, 104, 226, 235, 236, 297), then graph (LC 200, 207, 210, 261, 547).
- Weeks 5-7 — Dynamic programming: 30 problems, this is the longest stretch. Start 1D (70, 198, 322), then 2D-string (1143, 72), then 2D-grid (62, 64), then knapsack (416, 494, 518).
- Weeks 8-9 — Backtracking, heap, monotonic stack: 20 problems.
- Weeks 10-12 — Mock interviews + targeted weak-area: 12+ mocks (Pramp, Hello Interview, interviewing.io). Mock interview reveals what passes-without-pressure but fails-with-pressure.
Don't: blind-grind LeetCode 'easy' problems. The easy tier maps to junior bar and won't move you to senior+. Mediums are the senior interview floor; ~80% of senior coding rounds at FAANG are 'medium-tagged' problems with one curveball.
The book if you read one: Algorithm Design Manual (Skiena), 3rd ed. Skiena's chapter 6 (sorting/searching), 7 (graph traversal), 8 (weighted graphs), and 10 (DP) cover what interviews test. Cracking the Coding Interview is fine for first-time prep but doesn't go deep enough for senior+. CLRS is theoretical and the wrong tool for an interview-prep budget.
The mock interview that closes the gap: interviewing.io's anonymous mocks with current-employee FAANG senior+ engineers. Three of those, with the same problem you'd face on-site, surface the gap between 'I can solve it' and 'I can solve it under pressure'.
Frequently asked questions
- Is LeetCode 75 enough, or do I need NeetCode 150?
- LeetCode 75 covers the patterns at a junior-to-mid bar. NeetCode 150 (neetcode.io/practice) extends the same patterns to the harder problems senior+ rounds draw from. If you can solve all of NeetCode 150 mediums in under 30 minutes each, you're at the senior FAANG bar. Volume past 150 has diminishing returns; depth on the patterns matters more.
- Should I write the code in Python or C++?
- Python unless the role is systems / performance-critical (where C++ or Rust is more credible). Python's syntax is fastest to write, and you spend coding rounds on logic, not syntax. Use type hints (PEP 484) — they signal seniority. The exception: at companies whose primary stack is Java or Go (Google, Stripe), use that language; mismatch reads as inflexibility.
- How important is it to recognize the optimal solution immediately?
- Less important than candidates think. Most senior interviewers prefer 'state the brute force first, get to a working solution, then optimize' over 'jumped to the optimal solution and missed an edge case'. Articulating the path from O(n^2) to O(n) is graded; landing the O(n) solution silently is not.
- What if I freeze on a problem I haven't seen?
- Slow down and verbalize. 'I haven't seen this exact problem; let me try a small example by hand' is what senior+ candidates say. Walk through inputs of size 2, 3, 4 by hand. Identify what changes between inputs. The pattern reveals itself in 60-90% of cases. Silent struggle reads worse than transparent reasoning, even when the silent candidate eventually solves it.
- How does AI-augmented prep (Cursor + LeetCode) actually work?
- Use it for two specific things: (1) explain a solution you already wrote and disagree with, where the disagreement reveals your gap; (2) generate 5 variations of a problem you just solved, to stress-test the pattern. Do not use it as a first-pass solver — the gap between 'AI solved it' and 'I solved it under pressure' is the entire grade.
- Are bit manipulation and math problems still asked at FAANG in 2026?
- Less than they used to be. The Apple and Bloomberg interviews still draw from bit manipulation (LC 191 'Number of 1 Bits', 338 'Counting Bits'). Most other FAANG-tier rounds have moved to graph and DP problems. Spend ~5% of prep time on bit manipulation; not zero, but not the focus.
- How important is system-design fluency at the algorithms round?
- Zero in the algorithms round itself. But: senior+ interviews often have a coding round where the solution opens a system-design discussion. If you finish coding in 20 minutes, the interviewer may ask 'now, how would this scale to 100M users?' — be ready to switch modes. The two skills are tested separately but adjacent.
- Should I memorize Python's collections module?
- Yes. <code>collections.deque</code> for O(1) appends/pops both ends, <code>collections.Counter</code> for frequency maps, <code>collections.defaultdict(list)</code> for adjacency lists, <code>heapq</code> for priority queues. Knowing these saves 5-10 minutes per problem. Python's standard library is the load-bearing layer of fast interview coding.
Sources
- NeetCode 150 roadmap — pattern-based categorization of senior+ interview problems.
- LeetCode — Top Interview 150 study plan.
- Skiena — The Algorithm Design Manual, 3rd edition (the book to read for senior+ prep).
- Sedgewick & Wayne — Algorithms, 4th ed. (Princeton). Best DP coverage in print.
- interviewing.io — anonymous mock interviews with current-employee FAANG senior+ engineers.
- Hello Interview — coding mock pool and senior-bar feedback.
- Cracking the Coding Interview (McDowell), 6th ed. — fine for first-pass prep, not senior-bar by itself.
About the author. Blake Crosley founded ResumeGeni and writes about product design, hiring technology, and ATS optimization. More writing at blakecrosley.com.