gitGood.dev
Back to Blog

The 20 Coding Interview Patterns You Need to Know in 2026

G
gitGood Team
15 min read

Here's a secret that LeetCode power users know: there are only about 20 distinct patterns that cover the vast majority of coding interview questions. Once you recognize the pattern, the problem is half solved.

Stop grinding 500 random problems. Learn these 20 patterns, and you'll be able to tackle almost anything an interviewer throws at you.

How to Use This Guide

For each pattern, I'll cover:

  • What it is - The core idea
  • When to use it - The signals that tell you this pattern applies
  • Template - The code skeleton you can adapt
  • Classic problems - Where to practice

Pattern 1: Two Pointers

What it is: Use two pointers that move through an array or string, typically one from each end or both from the start at different speeds.

When to use it:

  • Sorted array or linked list
  • Finding pairs that satisfy a condition
  • Removing duplicates in-place
  • Comparing elements from both ends
def two_sum_sorted(nums, target):
    left, right = 0, len(nums) - 1
    while left < right:
        current_sum = nums[left] + nums[right]
        if current_sum == target:
            return [left, right]
        elif current_sum < target:
            left += 1
        else:
            right -= 1
    return [-1, -1]

Classic problems: Two Sum II, Container With Most Water, Trapping Rain Water, Valid Palindrome, 3Sum

Pattern 2: Sliding Window

What it is: Maintain a window of elements that slides through an array or string, expanding and contracting to find optimal subarrays/substrings.

When to use it:

  • "Longest/shortest substring with condition X"
  • "Maximum sum subarray of size K"
  • Contiguous sequence problems
def max_subarray_of_size_k(arr, k):
    window_sum = sum(arr[:k])
    max_sum = window_sum
    
    for i in range(k, len(arr)):
        window_sum += arr[i] - arr[i - k]
        max_sum = max(max_sum, window_sum)
    
    return max_sum

def longest_substring_without_repeating(s):
    char_set = set()
    left = 0
    max_len = 0
    
    for right in range(len(s)):
        while s[right] in char_set:
            char_set.remove(s[left])
            left += 1
        char_set.add(s[right])
        max_len = max(max_len, right - left + 1)
    
    return max_len

Classic problems: Longest Substring Without Repeating Characters, Minimum Window Substring, Max Consecutive Ones III, Fruit Into Baskets

What it is: Divide the search space in half each iteration. Not just for sorted arrays - works on any monotonic condition.

When to use it:

  • Sorted array lookup
  • "Find minimum/maximum that satisfies condition"
  • Search space can be divided in half
  • O(log n) is expected
def binary_search(arr, target):
    left, right = 0, len(arr) - 1
    while left <= right:
        mid = left + (right - left) // 2
        if arr[mid] == target:
            return mid
        elif arr[mid] < target:
            left = mid + 1
        else:
            right = mid - 1
    return -1

# Binary search on answer space
def min_capacity_to_ship(weights, days):
    left, right = max(weights), sum(weights)
    while left < right:
        mid = left + (right - left) // 2
        if can_ship(weights, days, mid):
            right = mid
        else:
            left = mid + 1
    return left

Classic problems: Search in Rotated Sorted Array, Find Minimum in Rotated Sorted Array, Koko Eating Bananas, Capacity To Ship Packages

What it is: Explore nodes level by level using a queue. Guarantees shortest path in unweighted graphs.

When to use it:

  • Shortest path in unweighted graph
  • Level-order traversal
  • "Minimum number of steps/moves"
  • Spreading/infection problems (rotten oranges, etc.)
from collections import deque

def bfs_shortest_path(graph, start, end):
    queue = deque([(start, 0)])
    visited = {start}
    
    while queue:
        node, distance = queue.popleft()
        if node == end:
            return distance
        for neighbor in graph[node]:
            if neighbor not in visited:
                visited.add(neighbor)
                queue.append((neighbor, distance + 1))
    
    return -1

def level_order_traversal(root):
    if not root:
        return []
    result = []
    queue = deque([root])
    while queue:
        level = []
        for _ in range(len(queue)):
            node = queue.popleft()
            level.append(node.val)
            if node.left:
                queue.append(node.left)
            if node.right:
                queue.append(node.right)
        result.append(level)
    return result

Classic problems: Binary Tree Level Order Traversal, Rotting Oranges, Word Ladder, Shortest Path in Binary Matrix

What it is: Explore as deep as possible before backtracking. Use stack (or recursion) to track the path.

When to use it:

  • Tree/graph traversal
  • Path finding (all paths, any path)
  • Connected components
  • Detecting cycles
def dfs_recursive(graph, node, visited=None):
    if visited is None:
        visited = set()
    visited.add(node)
    for neighbor in graph[node]:
        if neighbor not in visited:
            dfs_recursive(graph, neighbor, visited)
    return visited

def count_islands(grid):
    if not grid:
        return 0
    rows, cols = len(grid), len(grid[0])
    count = 0
    
    def dfs(r, c):
        if r < 0 or r >= rows or c < 0 or c >= cols or grid[r][c] == '0':
            return
        grid[r][c] = '0'
        dfs(r+1, c); dfs(r-1, c); dfs(r, c+1); dfs(r, c-1)
    
    for r in range(rows):
        for c in range(cols):
            if grid[r][c] == '1':
                count += 1
                dfs(r, c)
    return count

Classic problems: Number of Islands, Clone Graph, Path Sum, Course Schedule (cycle detection)

Pattern 6: Dynamic Programming (Bottom-Up)

What it is: Build solutions to larger problems from solutions to smaller subproblems. Store results to avoid recomputation.

When to use it:

  • "Count the number of ways"
  • "Find the minimum/maximum cost"
  • Optimal substructure + overlapping subproblems
  • Can't use greedy (no local optimal = global optimal)
# 1D DP: Climbing stairs
def climb_stairs(n):
    if n <= 2:
        return n
    dp = [0] * (n + 1)
    dp[1], dp[2] = 1, 2
    for i in range(3, n + 1):
        dp[i] = dp[i-1] + dp[i-2]
    return dp[n]

# 2D DP: Longest Common Subsequence
def lcs(text1, text2):
    m, n = len(text1), len(text2)
    dp = [[0] * (n + 1) for _ in range(m + 1)]
    for i in range(1, m + 1):
        for j in range(1, n + 1):
            if text1[i-1] == text2[j-1]:
                dp[i][j] = dp[i-1][j-1] + 1
            else:
                dp[i][j] = max(dp[i-1][j], dp[i][j-1])
    return dp[m][n]

Classic problems: Climbing Stairs, Coin Change, Longest Common Subsequence, Edit Distance, House Robber, 0/1 Knapsack

Pattern 7: Backtracking

What it is: Build candidates incrementally, abandoning a candidate ("backtracking") as soon as you determine it can't lead to a valid solution.

When to use it:

  • Generate all permutations/combinations
  • Solve constraint satisfaction (Sudoku, N-Queens)
  • "Find all possible..." problems
def permutations(nums):
    result = []
    def backtrack(current, remaining):
        if not remaining:
            result.append(current[:])
            return
        for i in range(len(remaining)):
            current.append(remaining[i])
            backtrack(current, remaining[:i] + remaining[i+1:])
            current.pop()
    backtrack([], nums)
    return result

def combination_sum(candidates, target):
    result = []
    def backtrack(start, current, remaining):
        if remaining == 0:
            result.append(current[:])
            return
        for i in range(start, len(candidates)):
            if candidates[i] > remaining:
                break
            current.append(candidates[i])
            backtrack(i, current, remaining - candidates[i])
            current.pop()
    candidates.sort()
    backtrack(0, [], target)
    return result

Classic problems: Permutations, Combination Sum, Subsets, N-Queens, Word Search, Palindrome Partitioning

Pattern 8: Heap / Priority Queue

What it is: A data structure that gives O(1) access to the min or max element and O(log n) insertion/deletion.

When to use it:

  • "Find the K largest/smallest"
  • Merge K sorted lists/arrays
  • Continuous median (two heaps)
  • Task scheduling by priority
import heapq

def k_largest(nums, k):
    # Min-heap of size k
    heap = nums[:k]
    heapq.heapify(heap)
    for num in nums[k:]:
        if num > heap[0]:
            heapq.heapreplace(heap, num)
    return sorted(heap, reverse=True)

def merge_k_sorted_lists(lists):
    heap = []
    for i, lst in enumerate(lists):
        if lst:
            heapq.heappush(heap, (lst[0].val, i, lst[0]))
    
    dummy = current = ListNode(0)
    while heap:
        val, i, node = heapq.heappop(heap)
        current.next = node
        current = current.next
        if node.next:
            heapq.heappush(heap, (node.next.val, i, node.next))
    return dummy.next

Classic problems: Kth Largest Element, Merge K Sorted Lists, Find Median from Data Stream, Top K Frequent Elements

Pattern 9: Monotonic Stack

What it is: A stack that maintains elements in increasing or decreasing order. Elements are popped when they violate the monotonic property.

When to use it:

  • "Next greater/smaller element"
  • Stock span problems
  • Largest rectangle in histogram
  • Temperature/weather problems
def next_greater_element(nums):
    result = [-1] * len(nums)
    stack = []  # stores indices
    
    for i in range(len(nums)):
        while stack and nums[i] > nums[stack[-1]]:
            result[stack.pop()] = nums[i]
        stack.append(i)
    
    return result

def daily_temperatures(temperatures):
    result = [0] * len(temperatures)
    stack = []
    
    for i, temp in enumerate(temperatures):
        while stack and temp > temperatures[stack[-1]]:
            prev = stack.pop()
            result[prev] = i - prev
        stack.append(i)
    
    return result

Classic problems: Daily Temperatures, Next Greater Element, Largest Rectangle in Histogram, Trapping Rain Water (stack approach)

Pattern 10: Hash Map Patterns

What it is: Use hash maps for O(1) lookups, frequency counting, and grouping.

When to use it:

  • Two Sum (unsorted)
  • Frequency counting / anagram detection
  • Grouping by key
  • Subarray sum equals K (prefix sum + hash map)
def two_sum(nums, target):
    seen = {}
    for i, num in enumerate(nums):
        complement = target - num
        if complement in seen:
            return [seen[complement], i]
        seen[num] = i
    return []

def group_anagrams(strs):
    groups = defaultdict(list)
    for s in strs:
        key = tuple(sorted(s))
        groups[key].append(s)
    return list(groups.values())

def subarray_sum_equals_k(nums, k):
    count = 0
    prefix_sum = 0
    seen = {0: 1}
    for num in nums:
        prefix_sum += num
        count += seen.get(prefix_sum - k, 0)
        seen[prefix_sum] = seen.get(prefix_sum, 0) + 1
    return count

Classic problems: Two Sum, Group Anagrams, Subarray Sum Equals K, Longest Consecutive Sequence

Pattern 11: Union Find (Disjoint Set)

What it is: A data structure for tracking elements partitioned into disjoint sets. Supports near-O(1) union and find operations.

When to use it:

  • Connected components in a graph
  • "Are these two nodes connected?"
  • Kruskal's MST algorithm
  • Accounts merge / friend circles
class UnionFind:
    def __init__(self, n):
        self.parent = list(range(n))
        self.rank = [0] * n
        self.components = n
    
    def find(self, x):
        if self.parent[x] != x:
            self.parent[x] = self.find(self.parent[x])
        return self.parent[x]
    
    def union(self, x, y):
        px, py = self.find(x), self.find(y)
        if px == py:
            return False
        if self.rank[px] < self.rank[py]:
            px, py = py, px
        self.parent[py] = px
        if self.rank[px] == self.rank[py]:
            self.rank[px] += 1
        self.components -= 1
        return True

Classic problems: Number of Connected Components, Redundant Connection, Accounts Merge, Longest Consecutive Sequence

Pattern 12: Trie (Prefix Tree)

What it is: A tree-like data structure for storing strings, where each node represents a character. Shared prefixes share the same path.

When to use it:

  • Autocomplete / prefix matching
  • Word search in a grid
  • Longest common prefix
  • Spell checker
class TrieNode:
    def __init__(self):
        self.children = {}
        self.is_end = False

class Trie:
    def __init__(self):
        self.root = TrieNode()
    
    def insert(self, word):
        node = self.root
        for char in word:
            if char not in node.children:
                node.children[char] = TrieNode()
            node = node.children[char]
        node.is_end = True
    
    def search(self, word):
        node = self.root
        for char in word:
            if char not in node.children:
                return False
            node = node.children[char]
        return node.is_end
    
    def starts_with(self, prefix):
        node = self.root
        for char in prefix:
            if char not in node.children:
                return False
            node = node.children[char]
        return True

Classic problems: Implement Trie, Word Search II, Design Add and Search Words, Replace Words

Pattern 13: Intervals

What it is: Problems involving ranges/intervals that may overlap, be merged, or need scheduling.

When to use it:

  • Merge overlapping intervals
  • Insert into sorted intervals
  • Meeting room scheduling
  • "Minimum number of intervals to cover"
def merge_intervals(intervals):
    intervals.sort(key=lambda x: x[0])
    merged = [intervals[0]]
    
    for start, end in intervals[1:]:
        if start <= merged[-1][1]:
            merged[-1][1] = max(merged[-1][1], end)
        else:
            merged.append([start, end])
    
    return merged

def min_meeting_rooms(intervals):
    starts = sorted(i[0] for i in intervals)
    ends = sorted(i[1] for i in intervals)
    rooms = 0
    end_ptr = 0
    
    for start in starts:
        if start < ends[end_ptr]:
            rooms += 1
        else:
            end_ptr += 1
    
    return rooms

Classic problems: Merge Intervals, Insert Interval, Meeting Rooms I & II, Non-overlapping Intervals, Minimum Number of Arrows

Pattern 14: Topological Sort

What it is: Linear ordering of vertices in a DAG such that for every directed edge (u, v), u comes before v.

When to use it:

  • Task/course scheduling with prerequisites
  • Build order / dependency resolution
  • Detecting cycles in directed graphs
from collections import deque

def topological_sort(num_courses, prerequisites):
    graph = defaultdict(list)
    in_degree = [0] * num_courses
    
    for course, prereq in prerequisites:
        graph[prereq].append(course)
        in_degree[course] += 1
    
    queue = deque([i for i in range(num_courses) if in_degree[i] == 0])
    order = []
    
    while queue:
        node = queue.popleft()
        order.append(node)
        for neighbor in graph[node]:
            in_degree[neighbor] -= 1
            if in_degree[neighbor] == 0:
                queue.append(neighbor)
    
    return order if len(order) == num_courses else []

Classic problems: Course Schedule I & II, Alien Dictionary, Sequence Reconstruction

Pattern 15: Greedy

What it is: Make the locally optimal choice at each step, hoping it leads to a globally optimal solution.

When to use it:

  • Activity selection / interval scheduling
  • Huffman coding
  • When local optimal provably leads to global optimal
  • Fractional knapsack
def jump_game(nums):
    max_reach = 0
    for i, jump in enumerate(nums):
        if i > max_reach:
            return False
        max_reach = max(max_reach, i + jump)
    return True

def assign_cookies(children, cookies):
    children.sort()
    cookies.sort()
    child = cookie = 0
    while child < len(children) and cookie < len(cookies):
        if cookies[cookie] >= children[child]:
            child += 1
        cookie += 1
    return child

Classic problems: Jump Game, Gas Station, Task Scheduler, Partition Labels, Assign Cookies

Pattern 16: Linked List Techniques

What it is: Common manipulation techniques for linked lists: fast/slow pointers, reversal, merge.

When to use it:

  • Cycle detection (Floyd's algorithm)
  • Finding middle element
  • Reversing a linked list
  • Merging sorted lists
def has_cycle(head):
    slow = fast = head
    while fast and fast.next:
        slow = slow.next
        fast = fast.next.next
        if slow == fast:
            return True
    return False

def reverse_list(head):
    prev = None
    current = head
    while current:
        next_node = current.next
        current.next = prev
        prev = current
        current = next_node
    return prev

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

Classic problems: Reverse Linked List, Linked List Cycle, Merge Two Sorted Lists, Remove Nth Node From End, Reorder List

Pattern 17: Bit Manipulation

What it is: Use bitwise operations for efficient computation and clever tricks.

When to use it:

  • Single number / find duplicate
  • Power of 2 checks
  • Counting bits
  • XOR tricks
def single_number(nums):
    result = 0
    for num in nums:
        result ^= num
    return result

def is_power_of_two(n):
    return n > 0 and (n & (n - 1)) == 0

def count_bits(n):
    count = 0
    while n:
        count += n & 1
        n >>= 1
    return count

Classic problems: Single Number, Number of 1 Bits, Counting Bits, Missing Number, Reverse Bits

Pattern 18: Graph - Shortest Path (Dijkstra's)

What it is: Find shortest paths from a source to all other vertices in a weighted graph with non-negative edges.

When to use it:

  • Weighted graph shortest path
  • Network delay time
  • Cheapest flights with K stops (modified)
import heapq

def dijkstra(graph, start, n):
    dist = [float('inf')] * n
    dist[start] = 0
    heap = [(0, start)]
    
    while heap:
        d, node = heapq.heappop(heap)
        if d > dist[node]:
            continue
        for neighbor, weight in graph[node]:
            new_dist = d + weight
            if new_dist < dist[neighbor]:
                dist[neighbor] = new_dist
                heapq.heappush(heap, (new_dist, neighbor))
    
    return dist

Classic problems: Network Delay Time, Cheapest Flights Within K Stops, Path with Minimum Effort, Swim in Rising Water

Pattern 19: Prefix Sum

What it is: Precompute cumulative sums to answer range sum queries in O(1).

When to use it:

  • Range sum queries
  • Subarray sum problems
  • 2D matrix sum queries
  • Finding equilibrium index
def range_sum(nums):
    prefix = [0] * (len(nums) + 1)
    for i in range(len(nums)):
        prefix[i + 1] = prefix[i] + nums[i]
    
    # Sum of nums[i:j+1] = prefix[j+1] - prefix[i]
    return prefix

def pivot_index(nums):
    total = sum(nums)
    left_sum = 0
    for i, num in enumerate(nums):
        if left_sum == total - left_sum - num:
            return i
        left_sum += num
    return -1

Classic problems: Range Sum Query, Product of Array Except Self, Subarray Sum Equals K, Contiguous Array

Pattern 20: Tree DFS Patterns

What it is: Specific DFS patterns on binary trees - inorder, preorder, postorder, and common tree problems.

When to use it:

  • Tree traversals
  • Tree validation (BST)
  • Lowest common ancestor
  • Maximum depth / diameter
def max_depth(root):
    if not root:
        return 0
    return 1 + max(max_depth(root.left), max_depth(root.right))

def is_valid_bst(root, min_val=float('-inf'), max_val=float('inf')):
    if not root:
        return True
    if root.val <= min_val or root.val >= max_val:
        return False
    return (is_valid_bst(root.left, min_val, root.val) and
            is_valid_bst(root.right, root.val, max_val))

def lowest_common_ancestor(root, p, q):
    if not root or root == p or root == q:
        return root
    left = lowest_common_ancestor(root.left, p, q)
    right = lowest_common_ancestor(root.right, p, q)
    if left and right:
        return root
    return left or right

Classic problems: Maximum Depth, Validate BST, Lowest Common Ancestor, Binary Tree Maximum Path Sum, Serialize and Deserialize Binary Tree

Study Plan: 6-Week Pattern Mastery

WeekPatternsProblems/Day
1Two Pointers, Sliding Window, Binary Search2-3 easy
2BFS, DFS, Tree DFS2-3 easy/medium
3Hash Map, Prefix Sum, Monotonic Stack2-3 medium
4DP, Backtracking, Greedy2-3 medium
5Heap, Intervals, Topo Sort, Union Find2-3 medium
6Trie, Linked List, Bit Manipulation, Dijkstra's2-3 medium/hard

The Pattern Recognition Trick

When you see a new problem, ask yourself these questions in order:

  1. Is the input sorted? → Two Pointers or Binary Search
  2. Is it about a subarray/substring? → Sliding Window or Prefix Sum
  3. Is it a tree or graph? → BFS or DFS
  4. Is it about optimization? → DP or Greedy
  5. Is it about generating all possibilities? → Backtracking
  6. Does it involve intervals? → Sort by start, use Intervals pattern
  7. Is it "Top K" or "Kth largest"? → Heap
  8. Is it about ordering with dependencies? → Topological Sort
  9. Is it about connected groups? → Union Find or DFS
  10. Is it about prefix matching? → Trie

Master these patterns, and you'll walk into any coding interview with confidence.