LeetCode Patterns: The 22 That Cover the Problem Set
A map of the LeetCode patterns worth learning: what triggers each one, a short Python template you can write from memory, and three problems to practise on.

LeetCode patterns are the reason two people who have solved the same number of problems can walk into the same interview with completely different odds. One has a catalogue of solutions and hopes the interviewer picks from it; the other has about twenty reusable techniques and spends three minutes deciding which one the constraints are asking for. This is a map of the second thing: twenty-two patterns, each with its trigger, a short Python template, and three problems to practise on.
Why the pattern is the unit of study#
A problem you have seen before is worth almost nothing in an interview, because the interviewer will change something. A pattern survives the change. It also changes what you can say when you are stuck: "I don't recognise this" is a dead end, while "this wants the longest contiguous stretch satisfying a condition and the constraints rule out O(n²), so it is a window" is a plan the interviewer can hint against.
How to read this map#
Each entry starts with its trigger — what in the statement or the constraints should make you reach for it. Read the constraints first, and ask for the bound on n out loud if it is missing; it is the cheapest hint in the interview. When two patterns fit, name both and pick the one whose correctness you can argue. A condensed lookup version lives in the coding interview cheat sheet.
The LeetCode patterns for arrays and strings#
These seven cover more interview problems than the other fifteen combined.
Hash map#
Trigger: a nested loop asking "is there another element such that…", or any counting, grouping or de-duplication.
def group_by_signature(words):
groups = {}
for word in words:
key = "".join(sorted(word))
groups.setdefault(key, []).append(word)
return list(groups.values())The skill is choosing a key that collides exactly when two inputs belong together. O(n·k log k) for n words of length k.
Practise: Two Sum · Group Anagrams · Longest Consecutive Sequence · more
Two pointers#
Trigger: the input is sorted or can be, and you want a pair, a partition point, or an in-place rewrite in O(1) space.
def remove_duplicates(nums):
if not nums:
return 0
write = 1
for read in range(1, len(nums)):
if nums[read] != nums[write - 1]:
nums[write] = nums[read]
write += 1
return writeThe comparison says which pointer cannot be in the answer, so moving it discards a whole family of candidates. O(n) time, O(1) space.
Practise: Valid Palindrome · 3Sum · Container With Most Water · more
Sliding window#
Trigger: the answer is a contiguous stretch, and the brute force recomputes overlapping ranges.
def max_sum_of_length_k(nums, k):
window = sum(nums[:k])
best = window
for right in range(k, len(nums)):
window += nums[right] - nums[right - k]
best = max(best, window)
return bestThe running summary updates in O(1) as an index moves, which is what collapses the nested loop. O(n).
Practise: Longest Substring Without Repeating Characters · Longest Repeating Character Replacement · Minimum Window Substring · more
Prefix sum#
Trigger: many range queries over an array that does not change, or counting subarrays that hit a target.
def build_prefix(nums):
prefix = [0]
for value in nums:
prefix.append(prefix[-1] + value)
return prefix
def range_sum(prefix, left, right):
return prefix[right + 1] - prefix[left]Build once, then every range query is a subtraction. Build O(n), query O(1).
Practise: Subarray Sum Equals K · Range Sum Query - Immutable · Contiguous Array · more
Binary search#
Trigger: a sorted array, or a numeric answer in a known range where checking a candidate is cheaper than computing it.
def lower_bound(nums, target):
low, high = 0, len(nums)
while low < high:
mid = (low + high) // 2
if nums[mid] < target:
low = mid + 1
else:
high = mid
return lowSearch for a boundary, not a value, and the off-by-one errors disappear: the loop ends when the bounds meet. O(log n).
Practise: Binary Search · Search in Rotated Sorted Array · Koko Eating Bananas · more
Sorting#
Trigger: adjacency in sorted order makes the answer visible — overlaps, duplicates, closest pairs — or a greedy choice needs an order.
def reconstruct_queue(people):
people.sort(key=lambda person: (-person[0], person[1]))
queue = []
for person in people:
queue.insert(person[1], person)
return queueThe insight is in the key, never the sort. O(n log n), plus whatever the pass costs.
Practise: Merge Intervals · Largest Number · Queue Reconstruction by Height · more
Greedy#
Trigger: a local choice can be shown never to rule out an optimal completion — usually by an exchange argument.
def can_reach_end(nums):
furthest = 0
for i, jump in enumerate(nums):
if i > furthest:
return False
furthest = max(furthest, i + jump)
return TrueThe code is three lines; the work is the proof. If the exchange argument fails, this is dynamic programming in disguise. O(n).
Practise: Jump Game · Jump Game II · Partition Labels · more
Patterns for stacks, queues and lists#
Stack#
Trigger: the input nests, or a new element resolves against the most recent unmatched one.
from operator import add, sub, mul
OPS = {"+": add, "-": sub, "*": mul, "/": lambda a, b: int(a / b)}
def eval_rpn(tokens):
stack = []
for token in tokens:
if token in OPS:
right, left = stack.pop(), stack.pop()
stack.append(OPS[token](left, right))
else:
stack.append(int(token))
return stack[-1]Both failure modes matter: a close on an empty stack, and items left at the end. O(n).
Practise: Valid Parentheses · Evaluate Reverse Polish Notation · Simplify Path · more
Monotonic stack#
Trigger: the question is about the nearest larger or smaller element, or a span between a position and the first position that beats it.
def days_until_warmer(temperatures):
answer = [0] * len(temperatures)
stack = []
for i, degrees in enumerate(temperatures):
while stack and temperatures[stack[-1]] < degrees:
previous = stack.pop()
answer[previous] = i - previous
stack.append(i)
return answerEvery pop resolves one answer. Each index is pushed and popped once, so it is O(n) despite the inner loop.
Practise: Daily Temperatures · Largest Rectangle in Histogram · Trapping Rain Water · more
Linked list#
Trigger: nodes must be reordered in place, or you need a midpoint, a cycle check, or the nth node from the end in one pass.
def middle_node(head):
slow = fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
return slowTwo habits remove most bugs: a dummy node when the head can change, and saving next before overwriting it. O(n) time, O(1) space.
Practise: Reverse Linked List · Linked List Cycle · Reorder List · more
Patterns for trees and graphs#
Tree traversal#
Trigger: a binary tree, plus a decision about when the node is processed relative to its children.
def height(node):
if node is None:
return 0
return 1 + max(height(node.left), height(node.right))Postorder when a node's answer depends on its subtrees, inorder for sorted order on a BST, preorder when information flows down from the root. O(n).
Practise: Binary Tree Inorder Traversal · Validate Binary Search Tree · Diameter of Binary Tree · more
Depth-first search#
Trigger: you need everything reachable from a node — a component, a region, a set of paths.
def count_components(graph):
seen = set()
def visit(node):
seen.add(node)
for neighbour in graph[node]:
if neighbour not in seen:
visit(neighbour)
components = 0
for node in graph:
if node not in seen:
components += 1
visit(node)
return componentsMark nodes on discovery, never on pop. Directed-graph cycle detection needs three states: finished is not the same as on the current path. O(V + E).
Practise: Clone Graph · Pacific Atlantic Water Flow · Binary Tree Maximum Path Sum · more
Breadth-first search#
Trigger: the minimum number of steps on an unweighted graph, or an answer that is per level.
from collections import deque
def level_order(root):
if root is None:
return []
levels, queue = [], deque([root])
while queue:
level = []
for _ in range(len(queue)):
node = queue.popleft()
level.append(node.val)
for child in (node.left, node.right):
if child:
queue.append(child)
levels.append(level)
return levelsDraining exactly len(queue) nodes separates one level from the next. First arrival is the shortest path. O(V + E).
Practise: Binary Tree Level Order Traversal · Rotting Oranges · 01 Matrix · more
Matrix and grid#
Trigger: cells connected to their neighbours, or pure index mechanics — rotate, spiral, transpose.
def rotate_in_place(matrix):
n = len(matrix)
for r in range(n):
for c in range(r + 1, n):
matrix[r][c], matrix[c][r] = matrix[c][r], matrix[r][c]
for row in matrix:
row.reverse()A grid is a graph nobody built: write the neighbour offsets as a list and DFS, BFS and union-find apply unchanged. O(n²) here.
Practise: Number of Islands · Spiral Matrix · Rotate Image · more
Topological sort#
Trigger: prerequisites, dependencies, build order — or a question about whether a directed graph has a cycle.
def topological_order(graph):
"""Assumes a DAG. Cycle detection needs a third node state."""
seen, order = set(), []
def visit(node):
seen.add(node)
for neighbour in graph[node]:
if neighbour not in seen:
visit(neighbour)
order.append(node)
for node in graph:
if node not in seen:
visit(node)
return order[::-1]Appending after the descendants finish, then reversing, gives a valid order; cycles need a third state — see topological sort explained. O(V + E).
Practise: Course Schedule · Course Schedule II · Alien Dictionary · more
Union-find#
Trigger: edges arrive one at a time and you need connectivity, component counts, or "does this edge close a cycle".
def find(parent, x):
while parent[x] != x:
parent[x] = parent[parent[x]]
x = parent[x]
return x
def union(parent, a, b):
root_a, root_b = find(parent, a), find(parent, b)
if root_a == root_b:
return False
parent[root_b] = root_a
return Trueunion returning False means "already connected", which is the cycle test. Path compression keeps the trees flat; adding union by size gives the inverse-Ackermann bound.
Practise: Number of Provinces · Redundant Connection · Accounts Merge · more
Trie#
Trigger: queries about prefixes rather than whole words, or a search that should be pruned by a dictionary.
END = "$"
def build_trie(words):
root = {}
for word in words:
node = root
for char in word:
node = node.setdefault(char, {})
node[END] = True
return rootLookup costs the length of the word, not the size of the dictionary. O(total characters) to build.
Practise: Implement Trie (Prefix Tree) · Design Add and Search Words Data Structure · Word Search II · more
Patterns for search, state and numbers#
Heap and priority queue#
Trigger: the k largest, smallest or closest, or a loop that always takes the cheapest item.
from heapq import heappush, heappushpop
def k_closest_to_origin(points, k):
heap = []
for x, y in points:
item = (-(x * x + y * y), x, y)
if len(heap) < k:
heappush(heap, item)
else:
heappushpop(heap, item)
return [[x, y] for _, x, y in heap]A max-heap in Python is a min-heap of negated keys. Capping at k gives O(n log k), not O(n log n).
Practise: Kth Largest Element in an Array · K Closest Points to Origin · Find Median from Data Stream · more
Backtracking#
Trigger: the problem asks for the arrangements themselves — subsets, permutations, partitions, placements — and n is small.
def permutations(nums):
result, path, used = [], [], [False] * len(nums)
def explore():
if len(path) == len(nums):
result.append(path[:])
return
for i, value in enumerate(nums):
if used[i]:
continue
used[i] = True
path.append(value)
explore()
path.pop()
used[i] = False
explore()
return resultChoose, explore, un-choose — and copy the path when recording it, or later mutations corrupt the answer. Prune before the recursive call, not at the leaf.
Practise: Subsets · Permutations · Combination Sum · more
Dynamic programming#
Trigger: a count, maximum, minimum or feasibility question where greedy is provably wrong and the naive recursion repeats itself.
def coin_change(coins, amount):
best = [0] + [float("inf")] * amount
for value in range(1, amount + 1):
for coin in coins:
if coin <= value:
best[value] = min(best[value], best[value - coin] + 1)
return -1 if best[amount] == float("inf") else best[amount]All the work is in choosing the state. Write it as cached recursion first, convert to a table once it works. O(amount · len(coins)).
Practise: Climbing Stairs · Coin Change · Longest Common Subsequence · more
Bit manipulation#
Trigger: every element appears a fixed number of times except one, or n is at most about twenty and you need to iterate over subsets.
def all_subsets(nums):
result = []
for mask in range(1 << len(nums)):
subset = [nums[i] for i in range(len(nums)) if mask >> i & 1]
result.append(subset)
return resultAn integer's bits are a set. x & (x - 1) clears the lowest set bit, x & -x isolates it, XOR cancels pairs. O(2^n · n) here.
Practise: Single Number · Number of 1 Bits · Counting Bits · more
Math and number theory#
Trigger: constraints far too large to iterate, or a statement about digits, divisors, primes or remainders.
def primes_below(limit):
if limit < 3:
return []
sieve = [True] * limit
sieve[0] = sieve[1] = False
for candidate in range(2, int(limit ** 0.5) + 1):
if sieve[candidate]:
for multiple in range(candidate * candidate, limit, candidate):
sieve[multiple] = False
return [i for i, is_prime in enumerate(sieve) if is_prime]Compute the first six answers by hand and look for the closed form or the invariant. O(n log log n).
Practise: Pow(x, n) · Happy Number · Count Primes · more
The order to learn them in#
Not the order above — learn them in the order that makes the next one easier. First the array and string seven: most common, easiest to check by hand, and hash map and two pointers turn up inside everything later. Second stack, linked list and tree traversal, all of which are pointer and order discipline, and where tree recursion becomes the on-ramp to graphs. Third the graph family — DFS, BFS, grids, topological sort, union-find, trie — starting with grids, the friendliest graphs because you can draw them. Last backtracking and dynamic programming, with heap, bit manipulation and math alongside; attempting those two first is the most common reason people stall.
Three to five problems per pattern, spread over weeks, is enough for recognition to become automatic. For a ready-made sequence the Blind 75 walks most of this map in roughly that order, the NeetCode 150 covers the same ground with more repetition, and the full problem index is filterable if you would rather assemble your own set.
If you want a second pair of eyes during the real thing, Stealth Interview is a desktop app for macOS and Windows that reads the problem from a screenshot and works through it with you — the pattern, the code, and the time and space complexity — while staying invisible to screen sharing.
Frequently asked questions
- How many LeetCode patterns are there?
- There is no official number — the count depends on how finely you split them. This map uses twenty-two, which is granular enough that each one has a distinct template and coarse enough that you can hold the list in your head. Some guides collapse it to fourteen by merging DFS, BFS and tree traversal into one graph pattern; others expand it past thirty by splitting dynamic programming into its sub-shapes. What matters is that the set covers the problem space, not that it hits a particular number.
- What order should I learn the patterns in?
- Hash map, two pointers, sliding window, binary search and sorting first: they cover the largest share of easy and medium problems and they appear inside harder ones. Then stack, linked list and tree traversal. Then the graph family — DFS, BFS, grids, topological sort, union-find. Leave dynamic programming and backtracking until the rest are automatic, because both are much easier once recursion and state feel natural.
- How do I know which pattern a problem uses?
- Read the constraints before the statement. The bound on n tells you the complexity you are allowed, and the complexity narrows the pattern list to two or three candidates. A contiguous subarray with an O(n) budget is a sliding window; a pair in a sorted array is two pointers; n up to twenty with an exponential answer is backtracking or a bitmask. The statement then picks between the survivors.
- Is learning patterns the same as memorising solutions?
- No, and the difference shows up under follow-up questions. Memorising a solution gives you one problem. Learning a pattern gives you the trigger that suggests it, the invariant that makes it correct, and the template you adapt — which is what lets you handle the variant the interviewer invents on the spot. The test of whether you have the pattern is whether you can state why it is correct, not whether you can reproduce the code.
- Do I still need to solve hundreds of problems if I know the patterns?
- Fewer, but not none. The pattern tells you what to reach for; only practice makes the reaching fast enough to happen under pressure while a stranger watches. Roughly three to five problems per pattern, spread over time rather than done in one sitting, is enough to make recognition automatic. Solving forty more problems in a pattern you already own adds very little.
Keep reading

Monotonic Stack Explained: The Next Greater Element Template
A monotonic stack is an ordinary stack with one rule attached: its contents are kept sorted, and anything that would break the order is popped first. That…

The Sliding Window Algorithm: Template and Eight Worked Problems
The sliding window algorithm turns a nested loop over every subarray into a single pass with two indices. It is the highest-leverage pattern in interview…

Topological Sort Explained: Kahn's Algorithm and the DFS Version
A topological sort orders the nodes of a directed graph so that every edge points forwards — if u must happen before v, then u comes first. That is the whole…