12 LeetCode patterns that cover most coding interviews (with Python templates)
Most interview problems are variations of a dozen patterns. For each one: the signals that give it away, a short Python template that runs as written, its complexity, and the classic problems to practise next.
Most coding interview questions are not new problems. They are variations of about a dozen patterns, and the hard part is recognising which one you are looking at. This guide lists the twelve patterns that cover most LeetCode-style questions. For each one you get the signals that give it away, a short Python template that runs as written, its complexity, and the classic problems to practise next.
Learn to name the pattern first. Once you can say “this is a sliding window”, the code is usually the easy part.
1. Hash map lookup
Signals: “find two items that…”, counting, duplicates, “have we seen this before?”. The brute force compares every pair.
def two_sum(nums: list[int], target: int) -> list[int]:
seen = {} # value -> index
for i, num in enumerate(nums):
if target - num in seen:
return [seen[target - num], i]
seen[num] = i
return []
assert two_sum([2, 7, 11, 15], 9) == [0, 1]
assert two_sum([3, 2, 4], 6) == [1, 2]
Complexity: O(n) time, O(n) space. Practise: two sum, contains duplicate, valid anagram, group anagrams.
2. Two pointers
Signals: a sorted array, pairs or triples meeting a condition, comparing both ends, partitioning in place.
def is_palindrome(s: str) -> bool:
chars = [c.lower() for c in s if c.isalnum()]
left, right = 0, len(chars) - 1
while left < right:
if chars[left] != chars[right]:
return False
left += 1
right -= 1
return True
assert is_palindrome("A man, a plan, a canal: Panama")
assert not is_palindrome("race a car")
Complexity: O(n) time. Practise: valid palindrome, two sum II, 3Sum, container with most water. Our sliding window and two pointers guide goes deeper.
3. Sliding window
Signals: a contiguous subarray or substring; the longest, shortest or count of windows that satisfy a condition.
def longest_unique_substring(s: str) -> int:
last_seen = {}
start = best = 0
for end, ch in enumerate(s):
if last_seen.get(ch, -1) >= start:
start = last_seen[ch] + 1
last_seen[ch] = end
best = max(best, end - start + 1)
return best
assert longest_unique_substring("abcabcbb") == 3
assert longest_unique_substring("abba") == 2
Complexity: O(n) time. Practise: best time to buy and sell stock, longest repeating character replacement, minimum window substring.
4. Prefix sums
Signals: many range-sum queries, or “count subarrays whose sum equals k”.
def subarray_sum_count(nums: list[int], k: int) -> int:
counts = {0: 1} # prefix sum -> how often it has occurred
total = answer = 0
for num in nums:
total += num
answer += counts.get(total - k, 0)
counts[total] = counts.get(total, 0) + 1
return answer
assert subarray_sum_count([1, 1, 1], 2) == 2
assert subarray_sum_count([1, 2, 3], 3) == 2
Complexity: O(n) time, O(n) space. Practise: range sum query, subarray sum equals k, product of array except self.
5. Binary search
Signals: sorted input, or a yes/no question whose answer flips once (“the smallest x that works”).
def first_true(lo: int, hi: int, ok) -> int:
"""Smallest x in [lo, hi] with ok(x) true, assuming ok flips from False to True once."""
while lo < hi:
mid = (lo + hi) // 2
if ok(mid):
hi = mid
else:
lo = mid + 1
return lo
nums = [1, 3, 5, 7, 9]
assert first_true(0, len(nums), lambda i: i == len(nums) or nums[i] >= 6) == 3
assert first_true(1, 100, lambda x: x * x >= 50) == 8
Complexity: O(log n) checks. Practise: binary search, search in rotated sorted array, Koko eating bananas.
6. Monotonic stack
Signals: “next greater element”, “how many days until a warmer temperature”, spans and histograms.
def daily_temperatures(temps: list[int]) -> list[int]:
answer = [0] * len(temps)
stack = [] # indices still waiting for a warmer day
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
assert daily_temperatures([73, 74, 75, 71, 69, 72, 76, 73]) == [1, 1, 4, 2, 1, 1, 0, 0]
Complexity: O(n) time, because each index is pushed and popped once. Practise: valid parentheses, daily temperatures, largest rectangle in histogram.
7. Linked list: fast and slow pointers
Signals: cycles, the middle of a list, or the k-th node from the end.
class Node:
def __init__(self, val, nxt=None):
self.val, self.next = val, nxt
def has_cycle(head) -> bool:
slow = fast = head
while fast and fast.next:
slow, fast = slow.next, fast.next.next
if slow is fast:
return True
return False
a = Node(1, Node(2, Node(3)))
assert not has_cycle(a)
a.next.next.next = a.next
assert has_cycle(a)
Complexity: O(n) time, O(1) space. Practise: linked list cycle, middle of the linked list, reverse linked list, merge two sorted lists.
8. Tree depth-first search
Signals: anything on a binary tree: depth, paths, validity, subtree properties.
class TreeNode:
def __init__(self, val, left=None, right=None):
self.val, self.left, self.right = val, left, right
def max_depth(root) -> int:
if root is None:
return 0
return 1 + max(max_depth(root.left), max_depth(root.right))
tree = TreeNode(3, TreeNode(9), TreeNode(20, TreeNode(15), TreeNode(7)))
assert max_depth(tree) == 3
assert max_depth(None) == 0
Complexity: O(n) time, O(h) stack space for a tree of height h. Practise: maximum depth, invert binary tree, validate binary search tree, lowest common ancestor.
9. Breadth-first search
Signals: shortest path in an unweighted grid or graph, level-by-level traversal, “minimum number of steps”.
from collections import deque
def shortest_path(grid: list[str]) -> int:
"""Steps from top-left to bottom-right through '.' cells; -1 if unreachable."""
rows, cols = len(grid), len(grid[0])
queue, seen = deque([(0, 0, 0)]), {(0, 0)}
while queue:
r, c, steps = queue.popleft()
if (r, c) == (rows - 1, cols - 1):
return steps
for dr, dc in ((1, 0), (-1, 0), (0, 1), (0, -1)):
nr, nc = r + dr, c + dc
if 0 <= nr < rows and 0 <= nc < cols and grid[nr][nc] == "." and (nr, nc) not in seen:
seen.add((nr, nc))
queue.append((nr, nc, steps + 1))
return -1
assert shortest_path(["..#", "#..", "..."]) == 4
assert shortest_path([".#", "#."]) == -1
Complexity: O(V + E), which for a grid is O(rows × cols). Practise: binary tree level order traversal, rotting oranges, word ladder.
10. Graphs: counting connected parts
Signals: islands, provinces, “how many groups”, or whether everything is connected.
def count_islands(grid: list[list[str]]) -> int:
rows, cols = len(grid), len(grid[0])
seen = set()
def sink(r, c):
stack = [(r, c)]
while stack:
r, c = stack.pop()
if 0 <= r < rows and 0 <= c < cols and grid[r][c] == "1" and (r, c) not in seen:
seen.add((r, c))
stack.extend(((r + 1, c), (r - 1, c), (r, c + 1), (r, c - 1)))
islands = 0
for r in range(rows):
for c in range(cols):
if grid[r][c] == "1" and (r, c) not in seen:
islands += 1
sink(r, c)
return islands
assert count_islands([list("11000"), list("11000"), list("00100"), list("00011")]) == 3
Complexity: O(rows × cols). Practise: number of islands, clone graph, course schedule (topological sort).
11. Heap (top k)
Signals: “the k largest”, “the k most frequent”, merging sorted lists, a running median.
import heapq
from collections import Counter
def top_k_frequent(nums: list[int], k: int) -> list[int]:
counts = Counter(nums)
return [num for num, _ in heapq.nlargest(k, counts.items(), key=lambda item: item[1])]
assert top_k_frequent([1, 1, 1, 2, 2, 3], 2) == [1, 2]
Complexity: O(n log k) time. Practise: kth largest element, top k frequent elements, merge k sorted lists.
12. Dynamic programming
Signals: “how many ways”, “the minimum cost”, “the longest…”, where the answer is built from answers to smaller versions of the same problem.
def min_coins(coins: list[int], amount: int) -> int:
best = [0] + [float("inf")] * amount # best[x] = fewest coins that make x
for x in range(1, amount + 1):
for coin in coins:
if coin <= x:
best[x] = min(best[x], best[x - coin] + 1)
return best[amount] if best[amount] != float("inf") else -1
assert min_coins([1, 2, 5], 11) == 3
assert min_coins([2], 3) == -1
Complexity: O(amount × coins) time, O(amount) space. Practise: climbing stairs, house robber, coin change, longest common subsequence.
How to practise the patterns
- Learn one pattern at a time. Solve three or four problems from the same pattern in a row until the template feels automatic.
- Name the pattern before you code. Write one line, “sliding window, because the answer is a contiguous substring”, and only then start.
- Brute force first, then optimise. Say the brute force and its complexity out loud, then explain which repeated work the pattern removes.
- Mix them up. After the first pass, shuffle problems from different patterns so you practise recognising them, which is the skill interviews test.
- Review your misses. Keep a list of the problems you got wrong and the signal you missed, and revisit it every week.
For a week-by-week plan that covers all twelve, see the 8-week coding interview roadmap.