LeetCode Patterns

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.

The Stealth Interview Team11 min read
LeetCode Patterns: The 22 That Cover the Problem Set

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.

Python
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.

Python
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 write

The 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.

Python
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 best

The 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.

Python
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

Trigger: a sorted array, or a numeric answer in a known range where checking a candidate is cheaper than computing it.

Python
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 low

Search 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.

Python
def reconstruct_queue(people):
    people.sort(key=lambda person: (-person[0], person[1]))
    queue = []
    for person in people:
        queue.insert(person[1], person)
    return queue

The 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.

Python
def can_reach_end(nums):
    furthest = 0
    for i, jump in enumerate(nums):
        if i > furthest:
            return False
        furthest = max(furthest, i + jump)
    return True

The 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.

Python
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.

Python
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 answer

Every 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.

Python
def middle_node(head):
    slow = fast = head
    while fast and fast.next:
        slow = slow.next
        fast = fast.next.next
    return slow

Two 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.

Python
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

Trigger: you need everything reachable from a node — a component, a region, a set of paths.

Python
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 components

Mark 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

Trigger: the minimum number of steps on an unweighted graph, or an answer that is per level.

Python
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 levels

Draining 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.

Python
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.

Python
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".

Python
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 True

union 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.

Python
END = "$"

def build_trie(words):
    root = {}
    for word in words:
        node = root
        for char in word:
            node = node.setdefault(char, {})
        node[END] = True
    return root

Lookup 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.

Python
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.

Python
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 result

Choose, 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.

Python
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.

Python
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 result

An 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.

Python
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

Ace your next coding interview

Stealth Interview is a desktop app for macOS and Windows that reads the problem off your screen and answers with a working solution, a step-by-step explanation and its time and space complexity — while staying invisible to screen sharing.

Get Stealth Interview