September 8, 2025
18 min

Top 23 Leetcode Patterns to Simplify Interview Prep and Save Time

The 23 LeetCode patterns behind most interview questions: when to use each one, a canonical problem per pattern, and a drill plan that sticks.

By The Interview Coder team· Updated Jun 11, 2026

Most LeetCode problems are not new problems. They are old patterns wearing new costumes. Sliding window, two pointers, BFS, dynamic programming — a couple dozen techniques cover the bulk of what shows up in a coding interview.

This guide lists the 23 patterns worth knowing, when to reach for each one, and one canonical problem per pattern. Practice the idea, not the exact question text.

Why LeetCode Patterns Beat Problem-Grinding

Brute-force grinding treats every problem as unique. You memorize solutions, forget them, and start over. Pattern-based prep teaches the grammar behind the problems. Once you know the grammar, you can read and write sentences you have never seen.

Interviewers do not expect you to recall a specific LeetCode problem. They expect you to recognize which pattern fits the prompt, pick the right algorithm, and explain the trade-offs. The bulk of questions at Google, Meta, Apple, Netflix, and Amazon reuse the same 10 to 12 core patterns — different inputs, same moves.

What Pattern-Based Prep Gets You

Cut prep time by targeting high-leverage ideas and one representative problem per pattern.

Recognize the right approach faster when the prompt is new.

Build a mental model that transfers across arrays, strings, trees, and graphs.

Replace memorized answers with a process, which kills most interview anxiety.

Explain pattern choice and trade-offs clearly. That is half the score.

Cheat Sheet of Core LeetCode Patterns

Use this quick reference before a mock interview. Pick one representative problem per pattern and solve it until you can explain the idea in under two minutes.

Two pointers — two indexes moving toward each other or in tandem to cut work on arrays and linked lists. Canonical: 3Sum.

Sliding window — a fixed or dynamic window over an array or string for subarray and substring properties. Canonical: Longest Substring Without Repeating Characters.

Fast and slow pointers — two speeds through a linked structure to detect cycles or find midpoints. Canonical: Linked List Cycle.

Depth-first search — explore paths deeply for exhaustive search in trees and graphs. Canonical: Diameter of Binary Tree.

Breadth-first search — explore level by level for shortest paths and minimum steps. Canonical: Binary Tree Level Order Traversal.

Binary search — repeatedly halve a sorted search space. Canonical: Binary Search.

Merge intervals — sort intervals, then merge overlapping ranges. Canonical: Merge Intervals.

Topological sort — order tasks with dependencies and detect cycles. Canonical: Course Schedule.

Dynamic programming — store overlapping subproblem answers and build up. Canonical: Longest Increasing Subsequence.

Union find — disjoint sets for connectivity and fast merging. Canonical: Number of Provinces.

Monotonic stack — keep elements ordered to answer next-greater queries in one pass. Canonical: Daily Temperatures.

Backtracking — explore candidates, prune early, undo. Canonical: Word Search.

How Each Pattern Maps to Common LeetCode Categories

Not every pattern shows up equally across categories. Use these pairings to prioritize study time.

Arrays and strings: Two pointers, sliding window, monotonic stack, binary search, dynamic programming.

Trees: DFS, BFS, dynamic programming, binary search on tree properties.

Graphs: BFS, DFS, topological sort, union find.

Linked lists: Fast and slow pointers, two pointers, in-place reversal.

Intervals and scheduling: Merge intervals, greedy.

Backtracking problems: Strings, arrays, and matrix traversal.

23 LeetCode Patterns That Solve Over 1,000 Problems

1. Two Pointers: Find Pairs, Triples, or Subarrays With Two Moving Indices

The two-pointer technique uses two indices to scan an array or list from different ends or with a fixed separation. Use it for pair sums, sorted-array problems, removing duplicates, or shrinking/growing subarrays.

Sample LeetCode problem: 3Sum (15): find unique triplets that sum to zero.

Solution:

def threeSum(nums):
    nums.sort()
    result = []
    for i in range(len(nums) - 2):
        if i > 0 and nums[i] == nums[i - 1]:
            continue
        left, right = i + 1, len(nums) - 1
        while left < right:
            total = nums[i] + nums[left] + nums[right]
            if total < 0:
                left += 1
            elif total > 0:
                right -= 1
            else:
                result.append([nums[i], nums[left], nums[right]])
                while left < right and nums[left] == nums[left + 1]:
                    left += 1
                while left < right and nums[right] == nums[right - 1]:
                    right -= 1
                left += 1
                right -= 1
    return result

Why it works: Sort enables ordering and skipping of duplicates. Fix one index and use left/right to scan for complements. Move pointers based on the sum to prune the search without extra memory.

Time complexity: O(n^2)

Space complexity: O(1) extra (not counting output)

Ten similar LeetCode problems: 42, 26, 27, 344, 11, 19, 76, 167, 977, 18

2. Sliding Window: Track a Contiguous Block and Expand or Shrink Efficiently

Maintain a window [left, right] and update it as you scan. Use for longest/shortest subarray or substring problems that need aggregate values inside a contiguous block.

Sample LeetCode problem: Longest Substring Without Repeating Characters (3).

Solution:

def lengthOfLongestSubstring(s: str) -> int:
    char_map = {}
    left = 0
    max_length = 0
    for right in range(len(s)):
        if s[right] in char_map:
            left = max(left, char_map[s[right]] + 1)
        char_map[s[right]] = right
        max_length = max(max_length, right - left + 1)
    return max_length

Why it works: The map tracks last seen indexes. Move left only when a repeat would violate uniqueness; right always advances. Each character is processed at most twice.

Time complexity: O(n)

Space complexity: O(min(n, m)) where m is the charset size.

Ten similar LeetCode problems: 76, 209, 438, 567, 1004, 424, 30, 159, 340, 395

3. Binary Search: Halve the Search Space on Sorted Input

Binary search finds targets or boundaries in O(log n). Use on sorted arrays, monotonic functions, or to find thresholds (search on answer).

Sample LeetCode problem: Search in Rotated Sorted Array (33).

Solution:

def search(nums, target):
    left, right = 0, len(nums) - 1
    while left <= right:
        mid = left + (right - left) // 2
        if nums[mid] == target:
            return mid
        if nums[left] <= nums[mid]:
            if nums[left] <= target < nums[mid]:
                right = mid - 1
            else:
                left = mid + 1
        else:
            if nums[mid] < target <= nums[right]:
                left = mid + 1
            else:
                right = mid - 1
    return -1

Why it works: Determine which half is sorted at mid, then decide where the target must lie. Each iteration halves the candidate range.

Time complexity: O(log n)

Space complexity: O(1)

Ten similar LeetCode problems: 35, 162, 278, 441, 153, 69, 410, 875, 34, 744

4. Depth-First Search (DFS): Explore Paths Deeply Before Backtracking

DFS traverses trees or graphs by going as deep as possible, then backtracking. Use it to explore connected components, generate paths, or solve recursion-friendly problems.

Sample LeetCode problem: Number of Islands (200).

Solution:

def numIslands(grid):
    if not grid:
        return 0

    def dfs(i, j):
        if i < 0 or i >= len(grid) or j < 0 or j >= len(grid[0]) or grid[i][j] == '0':
            return
        grid[i][j] = '0'
        dfs(i + 1, j)
        dfs(i - 1, j)
        dfs(i, j + 1)
        dfs(i, j - 1)

    count = 0
    for i in range(len(grid)):
        for j in range(len(grid[0])):
            if grid[i][j] == '1':
                dfs(i, j)
                count += 1
    return count

Why it works: Mark visited cells and recursively flood-fill neighbors. Each cell is visited once.

Time complexity: O(m * n)

Space complexity: O(m * n) worst-case recursion

Ten similar LeetCode problems: 104, 100, 101, 226, 257, 145, 94, 102, 112, 113

5. Breadth-First Search (BFS): Layered Exploration for Shortest Steps

BFS visits nodes by distance (layers). Use it when you need the shortest paths in unweighted graphs or multi-source distances.

Sample LeetCode problem: 0-1 Matrix (542): distance to nearest zero.

Solution:

from collections import deque

def updateMatrix(mat):
    if not mat:
        return []
    rows, cols = len(mat), len(mat[0])
    queue = deque()
    for r in range(rows):
        for c in range(cols):
            if mat[r][c] == 0:
                queue.append((r, c))
            else:
                mat[r][c] = float('inf')
    while queue:
        r, c = queue.popleft()
        for dr, dc in [(0, 1), (1, 0), (0, -1), (-1, 0)]:
            nr, nc = r + dr, c + dc
            if 0 <= nr < rows and 0 <= nc < cols and mat[nr][nc] > mat[r][c] + 1:
                mat[nr][nc] = mat[r][c] + 1
                queue.append((nr, nc))
    return mat

Why it works: Initialize all sources, then expand outward. BFS guarantees that the first time you set a distance, it is the shortest.

Time complexity: O(m * n)

Space complexity: O(m * n)

Ten similar LeetCode problems: 200, 994, 127, 515, 116, 199, 310, 863, 1091, 752

6. Backtracking: Build Candidates and Abandon Dead Ends

Backtracking constructs solutions incrementally and prunes when constraints break. Use it for permutations, combinations, partitioning, and constraint search.

Sample LeetCode problem: Permutations (46).

Solution:

def permute(nums):
    result = []

    def backtrack(path, remaining):
        if not remaining:
            result.append(path)
            return
        for i in range(len(remaining)):
            backtrack(path + [remaining[i]], remaining[:i] + remaining[i + 1:])

    backtrack([], nums)
    return result

Why it works: Enumerate choices at each step, recurse, and remove choices on return. Prune early when constraints fail.

Time complexity: O(n!)

Space complexity: O(n) recursion plus output

Ten similar LeetCode problems: 39, 40, 51, 52, 77, 216, 78, 47, 131, 90

7. Dynamic Programming: Store Subproblem Results to Avoid Recompute

DP breaks problems into overlapping subproblems with optimal substructure, using memoization or tabulation to reuse results.

Sample LeetCode problem: House Robber (198).

Solution:

def rob(nums):
    if not nums:
        return 0
    if len(nums) == 1:
        return nums[0]
    dp = [0] * len(nums)
    dp[0] = nums[0]
    dp[1] = max(nums[0], nums[1])
    for i in range(2, len(nums)):
        dp[i] = max(dp[i - 1], dp[i - 2] + nums[i])
    return dp[-1]

Why it works: Use previous optimal values to compute the current optimal. Avoid repeated branching.

Time complexity: O(n)

Space complexity: O(n) (can be reduced to O(1) by storing two variables)

Ten similar LeetCode problems: 300, 62, 63, 70, 509, 322, 416, 139, 256, 213

8. Greedy Algorithms: Make the Local Best Choice to Reach a Global Result

Greedy picks the locally optimal action at each step when that leads to a global optimum. Use when choices don't require revisiting.

Sample LeetCode problem: Jump Game (55).

Solution:

def canJump(nums):
    max_reachable = 0
    for i in range(len(nums)):
        if i > max_reachable:
            return False
        max_reachable = max(max_reachable, i + nums[i])
        if max_reachable >= len(nums) - 1:
            return True
    return False

Why it works: Track the farthest reachable index. If an index is beyond reach, fail early. If you can reach the end, return true.

Time complexity: O(n)

Space complexity: O(1)

Ten similar LeetCode problems: 45, 621, 392, 135, 406, 763, 1029, 435, 452, 134

9. Hashing: Use Hash Tables for Fast Lookup and Counting

Hash tables provide average O(1) inserts and lookups. Use them for frequency counts, de-duplication, membership tests, and mapping values to indices.

Sample LeetCode problem: Two Sum (1).

Solution:

def twoSum(nums, target):
    num_map = {}
    for i, num in enumerate(nums):
        complement = target - num
        if complement in num_map:
            return [num_map[complement], i]
        num_map[num] = i
    return []

Why it works: Store seen values with positions. Check complements in constant time while scanning once.

Time complexity: O(n)

Space complexity: O(n)

10. Sorting: Preprocess to Simplify Relationships or Enable Two-Pointer Solutions

Sorting orders data and unlocks binary search, two-pointer, and merge strategies. Use when relative order matters or you need deterministic scanning.

Sample LeetCode problem: Sort an Array (912): implement merge sort.

Solution:

def sortArray(nums):
    if len(nums) <= 1:
        return nums
    mid = len(nums) // 2
    left_half = nums[:mid]
    right_half = nums[mid:]
    sortArray(left_half)
    sortArray(right_half)
    i = j = k = 0
    while i < len(left_half) and j < len(right_half):
        if left_half[i] < right_half[j]:
            nums[k] = left_half[i]
            i += 1
        else:
            nums[k] = right_half[j]
            j += 1
        k += 1
    while i < len(left_half):
        nums[k] = left_half[i]
        i += 1
        k += 1
    while j < len(right_half):
        nums[k] = right_half[j]
        j += 1
        k += 1
    return nums

Why it works: Divide and conquer sorts subarrays, then merges in linear time per level.

Time complexity: O(n log n)

Space complexity: O(n)

11. Fast and Slow Pointers: Detect Cycles and Find Midpoints Without Extra Space

Run two pointers at different speeds through a linked structure. Use to detect cycles, find cycle entry, or locate midpoints.

Canonical problem: Linked List Cycle (141): detect a loop by checking if pointers meet.

Why it works: If a cycle exists, the faster pointer will eventually lap the slower one. Use a follow-up to find the cycle start by resetting one pointer and advancing both at equal speed.

MAANG note: Recognize this when a question mentions cycle detection or asks for O(1) space.

12. Merge Intervals: Consolidate Overlapping Ranges Into a Minimal Set

Sort intervals by start time and merge by comparing the current interval's end with the next start.

Canonical problem: Merge Intervals (56): produce non-overlapping intervals covering all inputs.

Why it works: Sorting ensures you only need to compare each interval with the last merged interval, merging greedily when they overlap.

MAANG note: Useful for scheduling, calendar conflicts, and resource allocation questions.

13. Topological Sort: Order Tasks That Have Dependency Constraints

Topological sort produces a linear order of nodes in a DAG so that all edges go forward. Use for prerequisites, build orders, or any dependency graph.

Canonical problem: Course Schedule (207): can you finish all courses given the prerequisites?

Why it works: Repeatedly remove nodes with zero in-degree and append them to the order. If nodes remain with a nonzero in-degree, a cycle exists.

MAANG note: Present both Kahn's algorithm (BFS) and DFS postorder approaches in interviews.

14. Union-Find (Disjoint Set): Manage Dynamic Connectivity Efficiently

Union-Find groups elements into sets and supports union and find operations. Use it for connectivity, cycle detection, and grouping queries.

Canonical problem: Number of Provinces (547): count connected components from adjacency info.

Why it works: Path compression and union by rank keep operations near-constant amortized time.

MAANG note: Talk about how union-find simplifies repeated merge queries in social networks and connectivity tasks.

15. Monotonic Stack: Solve Next-Greater or Next-Smaller Queries in Linear Time

Maintain a stack that preserves a monotonic order so you can resolve multiple queries in a single pass.

Canonical problem: Daily Temperatures (739): for each day, find how many days until a warmer temperature.

Why it works: When a new element breaks monotonicity, it resolves answers for stacked elements; pop until order is restored.

MAANG note: Good for range queries, stock spans, and histogram-based maxima.

16. Dynamic Programming (Advanced): Recognize States, Transitions, and Optimal Substructure

Advanced DP problems require careful state design, transitions, and often dimension reduction. Look for sequence-based decisions, partitioning, or 2D states.

Canonical problem: Longest Increasing Subsequence (300): solve with the O(n log n) patience-sort method or O(n^2) DP.

Why it works: Model state clearly (index, last choice, remaining capacity), then build transitions. Optimize with binary search, monotonic queues, or bitsets when possible.

MAANG note: Explain trade-offs for time vs. memory and show how to move from naive recursion to optimized tabulation.

17. In-Place Linked List Reversal: Reverse Lists or Segments With Constant Space

Use prev and curr pointers and rewire next pointers to reverse a list or a sublist in one pass. Use when asked to reverse a full list, reverse between indices, or reverse nodes in k groups.

Canonical problem: Reverse Linked List (206).

Technique (template):

def reverse_linked_list(head):
    prev = None
    ptr = head
    while ptr:
        next_node = ptr.next
        ptr.next = prev
        prev = ptr
        ptr = next_node
    return prev

Why it works: You change pointers locally and advance; no extra memory required.

18. Top K Elements: Use Heaps to Keep K Best Items Efficiently

Use heaps when you need the k largest, k smallest, or k most frequent elements without full sorting.

Canonical problem: Kth Largest Element in an Array (215).

Coding template:

import heapq

def top_k_largest_elements(arr, k):
    if k <= 0 or not arr:
        return []
    min_heap = []
    for num in arr:
        heapq.heappush(min_heap, num)
        if len(min_heap) > k:
            heapq.heappop(min_heap)
    return min_heap

For the k smallest, push negated values into a max-heap and flip the sign on the way out.

Why it works: Maintain a heap of size k, push new candidates, and pop the worst when size exceeds k; overall cost O(n log k) instead of O(n log n).

19. Binary Tree Traversal: Visit Nodes in Preorder, Inorder, Postorder, or Level Order

Choose the traversal that matches the goal: inorder for sorted output, preorder for serialization, postorder for bottom-up processing, BFS for levels.

Canonical problem: Binary Tree Inorder Traversal (94).

Coding template:

def inorder(node):
    if not node:
        return
    inorder(node.left)
    # visit node
    inorder(node.right)

Preorder visits the node before recursing; postorder visits after both children. For level order, swap recursion for a queue and process nodes layer by layer.

Why it works: Pick the traversal that aligns with the processing order required by the problem.

20. Graphs and Matrices: Apply DFS, BFS, and Graph Algorithms to Grids and Adjacency Lists

Graphs and matrices map to similar traversal techniques. Use DFS for full exploration, BFS for shortest paths, and topological sort for dependency ordering. Treat each matrix cell as a node and its four neighbors as edges.

Canonical problem: Rotting Oranges (994).

Coding template:

from collections import deque

def multi_source_bfs(grid):
    m, n = len(grid), len(grid[0])
    queue = deque(
        (i, j) for i in range(m) for j in range(n) if grid[i][j] == 2
    )

    minutes = 0
    while queue:
        for _ in range(len(queue)):
            i, j = queue.popleft()
            for di, dj in [(-1, 0), (1, 0), (0, -1), (0, 1)]:
                ni, nj = i + di, j + dj
                if 0 <= ni < m and 0 <= nj < n and grid[ni][nj] == 1:
                    grid[ni][nj] = 2
                    queue.append((ni, nj))
        if queue:
            minutes += 1
    return minutes

Why it works: Seeding the queue with every rotten cell lets BFS expand all fronts at once, so each level equals one minute of spread. Translate matrix neighbors into graph edges when needed, and swap in DFS when the goal is full exploration instead of shortest distance.

21. Bit Manipulation: Use Bitwise Ops for Compact, Fast Arithmetic and Parity Checks

Use AND, OR, XOR, NOT, and shifts for counting bits, toggling flags, or finding missing numbers. Reach for it when a problem mentions binary representation, parity, or O(1) space tricks.

Canonical problem: Single Number (136).

Tips and templates: Understand a ^ b properties, use x & -x to isolate the lowest set bit, and use shifts for multiplying/dividing by two. XOR all numbers to find a single missing or single non-duplicate element.

22. Overlapping Intervals: Detect Conflicts, Merge Ranges, and Find Free Slots

Sort by start time, then merge or detect overlaps while scanning. Use for meeting rooms, insert-interval, or min-coverage problems.

Canonical problem: Insert Interval (57).

Coding template:

def process_intervals(intervals):
    intervals.sort(key=lambda x: x[0])
    result = []
    for interval in intervals:
        if not result or result[-1][1] < interval[0]:
            result.append(interval)
        else:
            result[-1][1] = max(result[-1][1], interval[1])
    return result

Why it works: Sorting makes overlap checks local to the last merged interval, letting you handle every interval in one pass.

23. Prefix Sum: Precompute Cumulative Sums to Answer Range Queries Quickly

Build a prefix array so sum(i..j) becomes prefix[j] - prefix[i-1], turning repeated subarray sum queries into O(1). Use when a problem asks many range-sum questions over a static array.

Canonical problem: Range Sum Query - Immutable (303).

Coding template:

def build_prefix_sum(arr):
    prefix = [0] * len(arr)
    prefix[0] = arr[0]
    for i in range(1, len(arr)):
        prefix[i] = prefix[i - 1] + arr[i]
    return prefix

def query_subarray_sum(prefix, i, j):
    if i == 0:
        return prefix[j]
    return prefix[j] - prefix[i - 1]

Why it works: Precomputing once in O(n) lets you answer each sum query in constant time, which is ideal when the query count is large.

How to Drill Patterns

A week of focused drilling beats a month of random problems. Here is the loop:

One pattern per day. Read the template, solve the canonical problem cold, then do two or three of the similar problems listed above.

Explain it out loud. If you cannot describe the pattern and its complexity in under two minutes, you do not own it yet. Active recall beats re-reading.

Mix under time pressure. After covering the patterns, do random timed sets where you do not know which pattern applies. Recognition is the skill interviews actually test.

Track your misses. Keep a list of problems you failed and which pattern they belonged to. Re-drill the weak patterns, not the comfortable ones.

Narrate in mocks. Run mock interviews where you state the pattern choice and trade-offs before coding. Interviewers grade the reasoning, not just the code.

If you want a fixed problem list to run this loop on, start with the Blind 75 — it covers most of these patterns with one or two problems each.

Related Reading

When Patterns Are Not Enough

Patterns get you most of the way. But interviews add pressure, and pressure makes people blank on patterns they know cold.

Interview Coder is a desktop app that reads the problem on your screen during a live interview and generates a working solution with reasoning you can follow. It ships with 20+ stealth features that keep it invisible to screen sharing and proctoring tools, and over 100K users run it today. Plans: Free at $0, Monthly Pro at $299, or Lifetime Pro at $799 one-time.

Drill the patterns first. Keep a safety net for the day it counts.

Related Blogs

Explore Our Similar Blogs

View All blogs
Take the Next Step

Ready to Pass Any SWE Interviews with 100% Undetectable AI?

Step into your next interview with AI support designed to stay completely undetectable.