AssistedVoice
All articles
Technical

15 LeetCode Patterns That Cover Most Problems

September 2, 2026 17 min readBy The Assisted Voice Team

Most coding interview problems are not random puzzles. They are variations on a finite set of structural patterns. Learning those patterns does not replace understanding — you still need to prove correctness and handle edge cases — but it collapses the search space when you read a new prompt. This guide covers fifteen genuinely distinct patterns, how to recognise each one, and what interviewers listen for beyond a green submission bar.

Key takeaways

  • Pattern recognition narrows the approach; communication and edge-case handling decide pass vs fail at the margin.
  • Each pattern has a signal in the problem statement — sorted input, contiguous subarray, k-th element, DAG dependencies, and so on.
  • State time and space complexity out loud before or immediately after coding.
  • Use the recognition table when stuck for thirty seconds — map cues to patterns, then validate with a small example.
  • A realistic study plan cycles patterns with spaced repetition, not random grinding.

How to use this guide

For each pattern below you will find recognition cues, the core mechanic, typical complexity, a short Python sketch, and two or three named LeetCode problems that exemplify the shape. Sketches are idiomatic and runnable in spirit — adapt variable names in the interview. After the patterns section you will find a cue-to-pattern table, a study plan, and a frank note on what interviewers actually score.

Read the recognition cue before the code every time. In an interview, the cue is what you say in the first thirty seconds: "This looks like a sliding window because we need the longest contiguous substring with at most k distinct characters." Then state complexity targets, then sketch the invariant, then code. The table at the start is your panic button when a problem feels unfamiliar — scan the left column against the prompt, pick the best match, and validate with a two-element example before committing.

Several patterns compose naturally. Meeting Rooms II is merge intervals plus a heap. Sliding window median combines sliding window with two heaps. Course Schedule is topological sort built on graph adjacency. When you learn a pattern, note which other patterns appear as sub-steps — combined problems show up often in senior loops and in harder LeetCode mediums.

Array and sequence patterns

Most array interview questions reduce to how indices move. The five patterns in this section cover the majority of linear scans without extra data structures, or with only constant extra space. When the input is sorted or can be sorted cheaply, start here before reaching for a hash map.

Two pointers

Signal: sorted array, pair sum, removing duplicates in place, or comparing from both ends. Core mechanic: maintain left and right indices; move the pointer that moves you toward the target condition. Complexity: O(n) time, O(1) extra space for in-place array work. Example problems: Two Sum II (sorted), 3Sum, Container With Most Water, Valid Palindrome.

def two_sum_sorted(nums: list[int], target: int) -> list[int]:
    left, right = 0, len(nums) - 1
    while left < right:
        s = nums[left] + nums[right]
        if s == target:
            return [left + 1, right + 1]
        if s < target:
            left += 1
        else:
            right -= 1
    return []

Sliding window

Signal: contiguous subarray or substring, longest/shortest with a constraint, or at-most-k distinct characters. Core mechanic: expand the right edge; shrink from the left while the window is invalid; track aggregate state in a hash map or counter. Complexity: O(n) time, O(k) space for character set size k. Example problems: Longest Substring Without Repeating Characters, Minimum Window Substring, Max Consecutive Ones III, Fruit Into Baskets.

def length_of_longest_substring(s: str) -> int:
    last = {}
    left = best = 0
    for right, ch in enumerate(s):
        if ch in last and last[ch] >= left:
            left = last[ch] + 1
        last[ch] = right
        best = max(best, right - left + 1)
    return best

Fast and slow pointers

Signal: linked list cycle, find middle, palindrome in list, or happy number style implicit sequence. Core mechanic: two pointers advance at different speeds; they meet inside a cycle or slow reaches the end. Complexity: O(n) time, O(1) space on linked lists. Example problems: Linked List Cycle, Middle of the Linked List, Palindrome Linked List, Find the Duplicate Number (Floyd on array).

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 False

Merge intervals

Signal: intervals overlapping, meeting rooms, insert interval. Core mechanic: sort by start time; merge if current overlaps previous end, else append. Complexity: O(n log n) for sort, O(n) merge pass. Example problems: Merge Intervals, Insert Interval, Non-overlapping Intervals, Meeting Rooms II (with heap variant).

def merge_intervals(intervals: list[list[int]]) -> list[list[int]]:
    intervals.sort(key=lambda x: x[0])
    merged = []
    for start, end in intervals:
        if not merged or start > merged[-1][1]:
            merged.append([start, end])
        else:
            merged[-1][1] = max(merged[-1][1], end)
    return merged

Cyclic sort

Signal: array of length n with numbers in range 1..n (or 0..n-1), find missing, duplicate, or first wrong index. Core mechanic: place each value at index value-1 by swapping; scan for index mismatch. Complexity: O(n) time, O(1) space. Example problems: Find All Numbers Disappeared in an Array, Find the Duplicate Number (swap variant), First Missing Positive (related in-place placement).

def find_missing(nums: list[int]) -> int:
    i = 0
    while i < len(nums):
        j = nums[i] - 1
        if nums[i] != nums[j]:
            nums[i], nums[j] = nums[j], nums[i]
        else:
            i += 1
    for i, v in enumerate(nums):
        if v != i + 1:
            return i + 1
    return len(nums) + 1

Linked list patterns

In-place linked list reversal

Signal: reverse entire list, reverse between m and n, or reverse k-group. Core mechanic: iterate with prev, curr, next pointers; rewire curr.next. Complexity: O(n) time, O(1) space. Example problems: Reverse Linked List, Reverse Linked List II, Swap Nodes in Pairs, Reverse Nodes in k-Group.

def reverse_list(head):
    prev = None
    curr = head
    while curr:
        nxt = curr.next
        curr.next = prev
        prev = curr
        curr = nxt
    return prev

Tree and graph patterns

Trees are graphs without cycles; many solutions generalise if you treat children as neighbours. Choose BFS when the question asks for shortest path in an unweighted setting or explicit level-by-level processing. Choose DFS when you must enumerate paths, mutate state during traversal, or detect cycles with a three-colour mark. Topological sort is the special case for dependency ordering on directed acyclic graphs.

Breadth-first search

Signal: level order traversal, shortest path in unweighted grid or graph, minimum steps. Core mechanic: queue of nodes with depth or distance; process layer by layer. Complexity: O(V + E) on graphs, O(n) on trees. Example problems: Binary Tree Level Order Traversal, Rotting Oranges, Word Ladder, Shortest Path in Binary Matrix.

from collections import deque

def level_order(root) -> list[list[int]]:
    if not root:
        return []
    q = deque([root])
    out = []
    while q:
        level = []
        for _ in range(len(q)):
            node = q.popleft()
            level.append(node.val)
            if node.left:
                q.append(node.left)
            if node.right:
                q.append(node.right)
        out.append(level)
    return out

Depth-first search

Signal: explore all paths, count islands, detect cycles in directed graph with state, tree depth. Core mechanic: recurse or use explicit stack; mark visited; backtrack when needed. Complexity: O(V + E) time, O(h) recursion depth for trees. Example problems: Number of Islands, Path Sum II, Clone Graph, Max Depth of Binary Tree.

def num_islands(grid: list[list[str]]) -> int:
    rows, cols = len(grid), len(grid[0])
    def dfs(r: int, c: int) -> None:
        if r < 0 or c < 0 or r >= rows or c >= cols or grid[r][c] != '1':
            return
        grid[r][c] = '0'
        dfs(r + 1, c); dfs(r - 1, c); dfs(r, c + 1); dfs(r, c - 1)
    count = 0
    for r in range(rows):
        for c in range(cols):
            if grid[r][c] == '1':
                dfs(r, c)
                count += 1
    return count

Topological sort

Signal: prerequisites, course schedule, build order, dependency resolution — directed acyclic graph implied. Core mechanic: Kahn BFS with indegree count, or DFS post-order stack. Complexity: O(V + E). Example problems: Course Schedule, Course Schedule II, Alien Dictionary (hard), Minimum Height Trees (tree centre variant).

from collections import deque, defaultdict

def can_finish(numCourses: int, prerequisites: list[list[int]]) -> bool:
    indeg = [0] * numCourses
    adj = defaultdict(list)
    for a, b in prerequisites:
        adj[b].append(a)
        indeg[a] += 1
    q = deque(i for i in range(numCourses) if indeg[i] == 0)
    seen = 0
    while q:
        u = q.popleft()
        seen += 1
        for v in adj[u]:
            indeg[v] -= 1
            if indeg[v] == 0:
                q.append(v)
    return seen == numCourses

Heap and priority patterns

Heaps solve "what is the best candidate right now?" at scale. Python's heapq module is a min-heap; negate values for max-heap behaviour. Three related patterns — two heaps, top-K, and k-way merge — share the same mental model: keep only the candidates that still matter and discard the rest.

Two heaps

Signal: running median, balance stream between lower and upper half. Core mechanic: max-heap for lower half, min-heap for upper; rebalance when sizes differ by more than one. Complexity: O(log n) per insert, O(1) median read. Example problems: Find Median from Data Stream, Sliding Window Median (with lazy deletion harder).

import heapq

class MedianFinder:
    def __init__(self):
        self.low = []   # max-heap via negation
        self.high = []  # min-heap

    def addNum(self, num: int) -> None:
        heapq.heappush(self.low, -num)
        heapq.heappush(self.high, -heapq.heappop(self.low))
        if len(self.high) > len(self.low):
            heapq.heappush(self.low, -heapq.heappop(self.high))

    def findMedian(self) -> float:
        if len(self.low) > len(self.high):
            return float(-self.low[0])
        return (-self.low[0] + self.high[0]) / 2

Top-K elements

Signal: k largest, k smallest, k most frequent, k closest points. Core mechanic: min-heap of size k for k largest (store negatives for max-heap pattern), or bucket sort when values bounded. Complexity: O(n log k) with heap, O(n) with buckets when applicable. Example problems: Kth Largest Element in an Array, Top K Frequent Elements, K Closest Points to Origin.

import heapq
from collections import Counter

def top_k_frequent(nums: list[int], k: int) -> list[int]:
    counts = Counter(nums)
    return heapq.nlargest(k, counts.keys(), key=counts.get)

K-way merge

Signal: merge k sorted lists or arrays, smallest range covering k lists. Core mechanic: seed min-heap with head of each list; pop smallest, push next from same list. Complexity: O(N log k) where N is total elements, k lists. Example problems: Merge k Sorted Lists, Smallest Range Covering Elements from k Lists, Find K Pairs with Smallest Sums.

import heapq

def merge_k_lists(lists):
    heap = []
    for i, node in enumerate(lists):
        if node:
            heapq.heappush(heap, (node.val, i, node))
    dummy = curr = type('Node', (), {'val': 0, 'next': None})()
    while heap:
        val, i, node = heapq.heappop(heap)
        curr.next = node
        curr = curr.next
        if node.next:
            heapq.heappush(heap, (node.next.val, i, node.next))
    return dummy.next

Search and combinatorial patterns

Binary search generalises to any monotonic predicate — not only sorted arrays. Backtracking generates combinatorial objects; prune early when partial paths cannot beat the current best. Dynamic programming overlaps with backtracking when subproblems repeat; if your recursive solution recomputes the same states, tabulate or memoise.

Modified binary search

Signal: sorted or rotated sorted array, find boundary, first/last occurrence, search in matrix, minimise maximum. Core mechanic: maintain lo/hi; discard half using monotonic predicate. Complexity: O(log n) per search dimension. Example problems: Search in Rotated Sorted Array, Find Minimum in Rotated Sorted Array, Koko Eating Bananas, Median of Two Sorted Arrays (hard split).

def search_rotated(nums: list[int], target: int) -> int:
    lo, hi = 0, len(nums) - 1
    while lo <= hi:
        mid = (lo + hi) // 2
        if nums[mid] == target:
            return mid
        if nums[lo] <= nums[mid]:
            if nums[lo] <= target < nums[mid]:
                hi = mid - 1
            else:
                lo = mid + 1
        else:
            if nums[mid] < target <= nums[hi]:
                lo = mid + 1
            else:
                hi = mid - 1
    return -1

Subsets and combinations

Signal: generate all subsets, combinations, permutations, or partition with choices. Core mechanic: backtracking with start index; include/exclude branch or swap for permutations. Complexity: O(2^n) subsets, O(n!) permutations — state this honestly. Example problems: Subsets, Combination Sum, Permutations, Letter Combinations of a Phone Number.

def subsets(nums: list[int]) -> list[list[int]]:
    out, path = [], []
    def backtrack(start: int) -> None:
        out.append(path[:])
        for i in range(start, len(nums)):
            path.append(nums[i])
            backtrack(i + 1)
            path.pop()
    backtrack(0)
    return out

Dynamic programming (knapsack and variants)

Signal: optimise over sequences of choices, pick items with weight/value, count ways, or longest subsequence with structure. Core mechanic: define state (index, capacity, last taken); transition from smaller subproblems; often space-optimise to one or two rows. Complexity: O(n × W) for 0/1 knapsack with capacity W. Example problems: Coin Change, Longest Increasing Subsequence, 0/1 Knapsack (classic), House Robber, Edit Distance.

def coin_change(coins: list[int], amount: int) -> int:
    dp = [float('inf')] * (amount + 1)
    dp[0] = 0
    for a in range(1, amount + 1):
        for c in coins:
            if c <= a:
                dp[a] = min(dp[a], dp[a - c] + 1)
    return dp[amount] if dp[amount] != float('inf') else -1

Talk through the pattern before you code

Interviewers score your narration as much as your syntax. Assisted Voice surfaces pattern hints and complexity reminders in real time while you explain your approach — 35 minutes free, 50 with a referral — across 50+ languages in live meetings or solo practice.

Start free — 35 minutes, no card

What interviewers score beyond a correct answer

A solution that passes hidden tests but was silent for twelve minutes often receives a no hire. Interviewers are proxying how you would work on their team: clarifying ambiguity, proposing a plan, implementing cleanly, and validating.

  • Clarifying questions: empty input, duplicates, negative numbers, integer overflow, sorted or unsorted assumptions.
  • Stating the approach before coding: name the pattern, outline data structures, and give expected complexity.
  • Complexity analysis: identify dominant loops; mention tradeoffs if you used extra space for speed.
  • Edge cases: walk through at least one trivial case and one tricky case after coding.
  • Testing: dry-run a small example on the board or verbally trace pointer moves.
  • Follow-ups: when asked to optimise, recognise whether a different pattern applies rather than patching blindly.

The margin where communication wins

Two candidates with the same optimal solution can receive different outcomes. The one who explained why they chose a hash map over a nested loop, called out O(n) space, and tested an empty array reads as senior. Pattern recognition is necessary but not sufficient.

A realistic study plan

Grinding random problems has diminishing returns. Cycle patterns deliberately so recognition becomes automatic under stress. The plan below assumes roughly an hour a day for six weeks; adjust pace to your timeline.

  1. 1Week 1 — Arrays: two pointers, sliding window, and merge intervals (3 problems each).
  2. 2Week 2 — Lists and sorting tricks: fast/slow, reversal, cyclic sort (3 problems each).
  3. 3Week 3 — Trees and graphs: BFS and DFS daily alternation; add topological sort mid-week.
  4. 4Week 4 — Heaps: top-K, two heaps, k-way merge (2–3 problems each).
  5. 5Week 5 — Binary search variants and backtracking/subsets (mixed set of 8 problems).
  6. 6Week 6 — Dynamic programming: start with 1D (House Robber, Coin Change), then 2D (Edit Distance); timed mock interviews twice.
  7. 7Ongoing — For each missed problem, write one sentence on the cue you ignored and redo it after 48 hours.

Track mistakes in a spreadsheet: pattern, cue missed, bug type (off-by-one, null pointer, wrong base case). Review the sheet before each mock. Quality of review beats problem count.

Pattern recognition is necessary but not sufficient

Patterns accelerate the first five minutes. They do not replace proof: you must still argue why the window invariant holds, why the graph is acyclic, or why your DP state covers all subproblems. Interviewers at strong companies deliberately choose twist problems that combine patterns — sliding window plus binary search on answer, or BFS with state compression.

If you recognise a pattern but cannot implement it cleanly, slow down and solve easier variants of the same pattern until the code is muscle memory. If you implement quickly but never explain, practise aloud until narration is automatic. That combination — recognition, correct code, and clear speech — is what separates a pass from a fail when the bar is tight.

Avoid pattern forcing

Not every problem fits a template on first read. If your chosen pattern needs five patches to handle the constraints, step back and ask whether another pattern or a brute-force baseline with optimisation is simpler.

Putting it together in the interview room

When the problem appears, read twice, restate in your own words, and scan the recognition table mentally. Pick a pattern, propose it to the interviewer, and confirm with a tiny example. Code in clear sections; after finishing, run your edge cases and state complexity. When stuck, articulate what you know — input size, brute force cost, partial structure — rather than freezing.

Time management matters: reserve two to three minutes for clarification, five for approach, fifteen to twenty for implementation, and five for testing and complexity. If implementation is going long, simplify — a working O(n log n) solution with clean code beats a unfinished O(n) attempt. Interviewers sometimes add constraints mid-problem; treat that as a signal to revisit pattern choice rather than to patch fragile code.

These fifteen patterns appear again and again because they mirror real computation: scanning sequences, exploring graphs, prioritising candidates, and reusing subproblem answers. Master the cues, practise speaking through them, and treat each interview problem as a design conversation with a deadline — not a secret password only LeetCode veterans know.

Practice this answer — 35 minutes free

Rehearse it with the copilot, then use the same help on the call. No credit card.

Start free — 35 minutes, no card

Frequently asked questions

There is no magic number. A focused set of 80 to 120 problems covering these fifteen patterns with spaced repetition beats 300 random solves. Prioritise redoing problems you missed.

The Assisted Voice Team

Written by the team behind Assisted Voice — the real-time AI interview copilot. We analyze interview processes at thousands of companies and roles to help candidates prepare, practice, and perform under pressure.

Popular interview guides

Keep reading