7 Python Interview Patterns: Two Pointers to Sliding Window

Updated Feb 13, 2026

Introduction

Coding interviews can feel overwhelming, but most problems follow recognizable patterns. Understanding these patterns transforms seemingly complex problems into manageable challenges. In this comprehensive guide, we’ll explore 7 essential patterns that appear repeatedly in technical interviews at companies like Google, Amazon, and Meta.

These patterns aren’t just tricks—they represent fundamental problem-solving approaches that reduce time complexity and demonstrate algorithmic thinking. Let’s dive deep into each pattern with practical examples, complexity analysis, and battle-tested solutions.


1. Two Pointers Pattern

Core Principle

The Two Pointers technique uses two indices (pointers) that traverse an array or string, either moving toward each other or in the same direction. This pattern typically reduces O(n2)O(n^2) brute force solutions to O(n)O(n) linear time.

When to use:
– Sorted arrays or strings
– Finding pairs with specific properties
– Removing duplicates in-place
– Palindrome checks

Classic Problem: Two Sum II (Sorted Array)

Problem: Given a 1-indexed sorted array, find two numbers that add up to a target value.

Algorithm Visualization

Step Left Pointer Right Pointer Sum Action
1 arr[1]=2 arr[7]=15 17 > 9 Move right pointer left
2 arr[1]=2 arr[6]=11 13 > 9 Move right pointer left
3 arr[1]=2 arr[5]=7 9 = 9 Found!

Complete Solution

from typing import List

def twoSum(numbers: List[int], target: int) -> List[int]:
    """
    Find two numbers that add up to target using two pointers.

    Time Complexity: O(n) - single pass through array
    Space Complexity: O(1) - only two pointer variables
    """
    left = 0  # Start pointer at beginning
    right = len(numbers) - 1  # Start pointer at end

    while left < right:
        current_sum = numbers[left] + numbers[right]

        if current_sum == target:
            # Return 1-indexed positions
            return [left + 1, right + 1]
        elif current_sum < target:
            # Need larger sum, move left pointer right
            left += 1
        else:
            # Need smaller sum, move right pointer left
            right -= 1

    # No solution found (problem guarantees solution exists)
    return []

# Test case
print(twoSum([2, 7, 11, 15], 9))  # Output: [1, 2]
print(twoSum([2, 3, 4], 6))        # Output: [1, 3]

Step-by-Step Execution

Input: numbers = [2, 7, 11, 15], target = 9

Initial: left=0 (val=2), right=3 (val=15)
Step 1: 2 + 15 = 17 > 9  right moves to 2
Step 2: 2 + 11 = 13 > 9  right moves to 1
Step 3: 2 + 7 = 9 = 9  FOUND! Return [1, 2]

Common Variations

Problem Difficulty Key Difference
3Sum Medium Add third pointer with outer loop
Container With Most Water Medium Calculate area at each step
Remove Duplicates Easy Same-direction pointers
Valid Palindrome Easy Compare characters at both ends

Pro Tip: Always check if the array is sorted. If not, consider whether sorting first (O(nlogn)O(n log n)) would still give you an efficient solution.


Enjoying this article? Get more like it delivered to your inbox. Subscribe to the newsletter

2. Sliding Window Pattern

Core Principle

The Sliding Window maintains a dynamic subarray/substring that “slides” across the data structure. This pattern optimizes problems requiring examination of contiguous elements.

Key concept: Instead of recalculating from scratch for each window position, we add the new element and remove the old element.

When to use:
– Finding subarrays/substrings with specific properties
– Maximum/minimum subarray problems
– String pattern matching

Classic Problem: Maximum Sum Subarray of Size K

Problem: Find the maximum sum of any contiguous subarray of size kk.

Naive vs. Optimized Approach

Approach Time Complexity Description
Brute Force O(n×k)O(n times k) Recalculate sum for each window
Sliding Window O(n)O(n) Subtract left, add right element

Complete Solution

def max_sum_subarray(arr: List[int], k: int) -> int:
    """
    Find maximum sum of subarray with size k using sliding window.

    Time Complexity: O(n) - single pass after initial window
    Space Complexity: O(1) - only store window sum and max
    """
    if len(arr) < k:
        return 0  # Edge case: array smaller than k

    # Calculate sum of first window
    window_sum = sum(arr[:k])
    max_sum = window_sum

    # Slide the window from left to right
    for i in range(k, len(arr)):
        # Remove leftmost element of previous window
        window_sum -= arr[i - k]
        # Add new element entering the window
        window_sum += arr[i]
        # Update maximum if current window sum is larger
        max_sum = max(max_sum, window_sum)

    return max_sum

# Test case
print(max_sum_subarray([2, 1, 5, 1, 3, 2], 3))  # Output: 9
print(max_sum_subarray([2, 3, 4, 1, 5], 2))     # Output: 7

Execution Visualization

Input: arr = [2, 1, 5, 1, 3, 2], k = 3

Window 1: [2, 1, 5] → sum = 8
Window 2: [1, 5, 1] → sum = 8 - 2 + 1 = 7
Window 3: [5, 1, 3] → sum = 7 - 1 + 3 = 9 ← MAX
Window 4: [1, 3, 2] → sum = 9 - 5 + 2 = 6

Result: 9

Dynamic Sliding Window

For problems where window size varies, use two pointers to adjust window boundaries:

def longest_substring_k_distinct(s: str, k: int) -> int:
    """
    Find longest substring with at most k distinct characters.

    Time Complexity: O(n) - each character visited at most twice
    Space Complexity: O(k) - hashmap stores at most k+1 characters
    """
    from collections import defaultdict

    char_count = defaultdict(int)
    left = 0
    max_length = 0

    for right in range(len(s)):
        # Expand window by adding right character
        char_count[s[right]] += 1

        # Contract window while constraint violated
        while len(char_count) > k:
            char_count[s[left]] -= 1
            # Remove character from map when count becomes 0 to maintain accurate distinct count
            if char_count[s[left]] == 0:
                del char_count[s[left]]
            left += 1

        # Update maximum length
        max_length = max(max_length, right - left + 1)

    return max_length

# Test case
print(longest_substring_k_distinct("eceba", 2))  # Output: 3 ("ece")
print(longest_substring_k_distinct("aa", 1))     # Output: 2 ("aa")

Common Mistakes

  • ❌ Forgetting to handle edge cases (array size < k)
  • ❌ Not resetting window state properly
  • ❌ Using fixed window size when dynamic window needed

3. Fast & Slow Pointers (Tortoise and Hare)

Core Principle

The Fast & Slow Pointers (Floyd’s Cycle Detection) uses two pointers moving at different speeds. This pattern detects cycles and finds middle elements efficiently.

Key insight: If there’s a cycle, the fast pointer will eventually catch up to the slow pointer.

When to use:
– Cycle detection in linked lists
– Finding middle element
– Detecting palindrome in linked lists

Classic Problem: Linked List Cycle Detection

Problem: Determine if a linked list has a cycle.

Mathematical Proof

Let’s say the cycle starts at position CC:

  • Slow pointer moves 11 step per iteration
  • Fast pointer moves 22 steps per iteration
  • Speed difference: 21=12 – 1 = 1 step per iteration

If a cycle exists with length LL, once both pointers are inside the cycle, the fast pointer gains 11 step on the slow pointer each iteration. The relative distance between them decreases by 11 each step, so they must meet within at most LL iterations after both enter the cycle.

Meeting Time=O(n)text{Meeting Time} = O(n)

where nn is the number of nodes.

Complete Solution

class ListNode:
    def __init__(self, val=0, next=None):
        self.val = val
        self.next = next

def hasCycle(head: ListNode) -> bool:
    """
    Detect cycle in linked list using fast & slow pointers.

    Time Complexity: O(n) - visit each node at most once
    Space Complexity: O(1) - only two pointer variables
    """
    if not head or not head.next:
        return False  # Empty list or single node has no cycle

    slow = head       # Moves 1 step at a time
    fast = head.next  # Moves 2 steps at a time

    while slow != fast:
        # If fast reaches end, no cycle exists
        if not fast or not fast.next:
            return False

        slow = slow.next       # Move slow pointer 1 step
        fast = fast.next.next  # Move fast pointer 2 steps

    # Pointers met, cycle detected
    return True

Visualization

No Cycle:

1  2  3  4  None
           
   slow    fast

Fast reaches None  No cycle

With Cycle:

1  2  3  4
           
    ←───────┘

Iteration 1: slow=2, fast=4
Iteration 2: slow=3, fast=3  CYCLE DETECTED

Advanced: Find Cycle Start Point

def detectCycle(head: ListNode) -> ListNode:
    """
    Find the node where cycle begins.

    Time Complexity: O(n)
    Space Complexity: O(1)
    """
    slow = fast = head

    # Phase 1: Detect if cycle exists
    while fast and fast.next:
        slow = slow.next
        fast = fast.next.next

        if slow == fast:
            # Phase 2: Find cycle start
            # Reset slow to head, move both at same speed
            slow = head
            while slow != fast:
                slow = slow.next
                fast = fast.next
            return slow  # Cycle start node

    return None  # No cycle

Why this works: When pointers meet, the distance from head to cycle start equals the distance from meeting point to cycle start.

Problem Difficulty Pattern Usage
Middle of Linked List Easy Fast moves 2x, slow at middle when fast ends
Palindrome Linked List Easy Find middle, reverse second half, compare
Happy Number Easy Detect cycle in number transformations

4. Merge Intervals Pattern

Core Principle

The Merge Intervals pattern handles overlapping intervals by sorting and merging. This approach transforms chaotic interval data into organized, non-overlapping ranges.

Key steps:
1. Sort intervals by start time: O(nlogn)O(n log n)
2. Merge overlapping intervals in single pass: O(n)O(n)

When to use:
– Calendar scheduling problems
– Range merging
– Interval intersection/union operations

Classic Problem: Merge Overlapping Intervals

Problem: Given intervals [[1,3],[2,6],[8,10],[15,18]], merge overlapping ones.

Algorithm Steps

Input: [[1,3], [2,6], [8,10], [15,18]]

1. Sort by start: [[1,3], [2,6], [8,10], [15,18]] (already sorted)

2. Merge process:
   Result: [[1,3]]
   Check [2,6]: 2 ≤ 3 → OVERLAP → Merge to [1,6]
   Result: [[1,6]]
   Check [8,10]: 8 > 6 → NO OVERLAP → Add separately
   Result: [[1,6], [8,10]]
   Check [15,18]: 15 > 10 → NO OVERLAP → Add separately
   Result: [[1,6], [8,10], [15,18]]

Complete Solution

def merge(intervals: List[List[int]]) -> List[List[int]]:
    """
    Merge all overlapping intervals.

    Time Complexity: O(n log n) - dominated by sorting
    Space Complexity: O(n) - result array (O(log n) for sorting)
    """
    if not intervals:
        return []  # Edge case: empty input

    # Sort intervals by start time
    intervals.sort(key=lambda x: x[0])

    merged = [intervals[0]]  # Initialize with first interval

    for current in intervals[1:]:
        last = merged[-1]  # Get last merged interval

        # Check if current overlaps with last merged interval
        if current[0] <= last[1]:
            # Overlap exists: merge by extending end time
            last[1] = max(last[1], current[1])
        else:
            # No overlap: add as separate interval
            merged.append(current)

    return merged

# Test cases
print(merge([[1,3], [2,6], [8,10], [15,18]]))  # [[1,6], [8,10], [15,18]]
print(merge([[1,4], [4,5]]))                    # [[1,5]]
print(merge([[1,4], [0,4]]))                    # [[0,4]]

Visual Execution

Timeline visualization:

[1,3]:   |---|
[2,6]:     |-----|
[8,10]:           |--|
[15,18]:               |---|

Merged:
[1,6]:   |--------|
[8,10]:           |--|
[15,18]:               |---|

Common Edge Cases

Case Example Handling
Same start time [[1,4], [1,5]] Take max(end)
Nested intervals [[1,10], [2,3]] Larger interval absorbs smaller
Adjacent intervals [[1,2], [2,3]] Counts as overlap (shared endpoint)
Empty input [] Return [] immediately

Advanced Variation: Insert Interval

def insert(intervals: List[List[int]], newInterval: List[int]) -> List[List[int]]:
    """
    Insert new interval and merge if necessary.

    Time Complexity: O(n) - single pass (input already sorted)
    Space Complexity: O(n) - result array
    """
    result = []
    i = 0
    n = len(intervals)

    # Phase 1: Add all intervals that end before newInterval starts
    while i < n and intervals[i][1] < newInterval[0]:
        result.append(intervals[i])
        i += 1

    # Phase 2: Merge all overlapping intervals with newInterval
    while i < n and intervals[i][0] <= newInterval[1]:
        newInterval[0] = min(newInterval[0], intervals[i][0])
        newInterval[1] = max(newInterval[1], intervals[i][1])
        i += 1
    result.append(newInterval)

    # Phase 3: Add remaining intervals
    while i < n:
        result.append(intervals[i])
        i += 1

    return result

# Test case
print(insert([[1,3], [6,9]], [2,5]))  # [[1,5], [6,9]]
print(insert([[1,2], [3,5], [6,7], [8,10], [12,16]], [4,8]))  # [[1,2], [3,10], [12,16]]

5. Cyclic Sort Pattern

Core Principle

Cyclic Sort exploits array indices when elements are in range [1,n][1, n] or [0,n1][0, n-1]. The idea: place each number at its “correct” index position.

Key insight: For array of size nn with numbers 11 to nn, number kk should be at index k1k-1.

Correct index for value k=k1text{Correct index for value } k = k – 1

When to use:
– Finding missing/duplicate numbers
– Array elements in known range
O(1)O(1) space constraint

Classic Problem: Find Missing Number

Problem: Given array containing nn distinct numbers in range [0,n][0, n], find the missing number.

Algorithm Approach Comparison

Approach Time Space Method
Sorting O(nlogn)O(n log n) O(1)O(1) Sort and scan for gap
Hash Set O(n)O(n) O(n)O(n) Store all, check 0 to n
Sum Formula O(n)O(n) O(1)O(1) n(n+1)2arrfrac{n(n+1)}{2} – sum{arr}
Cyclic Sort O(n)O(n) O(1)O(1) Place in correct positions

Complete Solution

def missingNumber(nums: List[int]) -> int:
    """
    Find missing number using cyclic sort.

    Time Complexity: O(n) - each number placed once
    Space Complexity: O(1) - in-place swapping
    """
    n = len(nums)
    i = 0

    # Phase 1: Place each number at its correct index
    while i < n:
        correct_index = nums[i]  # For range [0,n], number k belongs at index k

        # If number is in range and not at correct position
        if nums[i] < n and nums[i] != nums[correct_index]:
            # Swap to place at correct index
            nums[i], nums[correct_index] = nums[correct_index], nums[i]
        else:
            i += 1

    # Phase 2: Find first index where number doesn't match
    for i in range(n):
        if nums[i] != i:
            return i  # This index number is missing

    # If all indices match, missing number is n
    return n

# Test cases
print(missingNumber([3, 0, 1]))           # Output: 2
print(missingNumber([0, 1]))              # Output: 2
print(missingNumber([9,6,4,2,3,5,7,0,1])) # Output: 8

Step-by-Step Execution

Input: nums = [3, 0, 1]

Initial: [3, 0, 1]
         ↑
         i=0

Step 1: nums[0]=3, correct_index=3 (out of bounds)
        No swap, move i → [3, 0, 1], i=1

Step 2: nums[1]=0, correct_index=0, nums[0]≠0
        Swap → [0, 3, 1], i stays 1

Step 3: nums[1]=3, correct_index=3 (out of bounds)
        No swap, move i → [0, 3, 1], i=2

Step 4: nums[2]=1, correct_index=1, nums[1]≠1
        Swap → [0, 1, 3], i stays 2

Step 5: nums[2]=3, correct_index=3 (out of bounds)
        No swap, move i → [0, 1, 3], i=3 (exit loop)

Phase 2: Check indices
         nums[0]=0 ✓, nums[1]=1 ✓, nums[2]=3 ✗
         Return 2

Advanced: Find All Duplicates

def findDuplicates(nums: List[int]) -> List[int]:
    """
    Find all numbers appearing twice in array [1, n].

    Time Complexity: O(n)
    Space Complexity: O(1) excluding result array
    """
    i = 0

    # Cyclic sort: place each number at index (number - 1)
    while i < len(nums):
        correct_index = nums[i] - 1

        if nums[i] != nums[correct_index]:
            nums[i], nums[correct_index] = nums[correct_index], nums[i]
        else:
            i += 1

    # Find duplicates: numbers not at their correct position
    duplicates = []
    for i in range(len(nums)):
        if nums[i] != i + 1:
            duplicates.append(nums[i])

    return duplicates

# Test case
print(findDuplicates([4,3,2,7,8,2,3,1]))  # [2, 3]

Pattern Recognition

Use Cyclic Sort when:
– Array contains numbers in range [1,n][1, n] or [0,n][0, n]
– Problem asks for missing/duplicate/smallest missing positive
– Need O(1)O(1) space complexity


6. In-Place Reversal of Linked List

Core Principle

In-Place Reversal modifies linked list structure by reversing pointers without extra memory. This pattern requires careful tracking of previous, current, and next nodes.

Key challenge: Don’t lose reference to rest of the list while reversing.

When to use:
– Reversing linked lists (full or partial)
– Rotating lists
– Reordering list nodes

Classic Problem: Reverse Linked List

Problem: Reverse a singly linked list.

Pointer Tracking Strategy

We need three pointers at each step:

  1. prev: Previously processed node
  2. current: Node being processed
  3. next: Next node to process (prevent losing reference)

Complete Solution

def reverseList(head: ListNode) -> ListNode:
    """
    Reverse linked list in-place.

    Time Complexity: O(n) - visit each node once
    Space Complexity: O(1) - only pointer variables
    """
    prev = None
    current = head

    while current:
        # Step 1: Save next node (prevent losing reference)
        next_node = current.next

        # Step 2: Reverse current node's pointer
        current.next = prev

        # Step 3: Move prev and current one step forward
        prev = current
        current = next_node

    # prev now points to new head
    return prev

Visual Execution

Input: 1 → 2 → 3 → 4 → None

Initial:
prev = None, current = 1

Iteration 1:
  next_node = 2
  1.next = None
  prev = 1, current = 2
  Result: None  1    2  3  4  None

Iteration 2:
  next_node = 3
  2.next = 1
  prev = 2, current = 3
  Result: None  1  2    3  4  None

Iteration 3:
  next_node = 4
  3.next = 2
  prev = 3, current = 4
  Result: None  1  2  3    4  None

Iteration 4:
  next_node = None
  4.next = 3
  prev = 4, current = None
  Result: None  1  2  3  4

Return prev (4)
Final: 4  3  2  1  None

Recursive Solution

def reverseListRecursive(head: ListNode) -> ListNode:
    """
    Reverse linked list recursively.

    Time Complexity: O(n)
    Space Complexity: O(n) - recursion call stack
    """
    # Base case: empty list or single node
    if not head or not head.next:
        return head

    # Recursively reverse rest of list
    new_head = reverseListRecursive(head.next)

    # Reverse current node's connection
    head.next.next = head  # Next node points back to current
    head.next = None       # Current node points to None

    return new_head

Advanced: Reverse Sublist (Between Positions)

def reverseBetween(head: ListNode, left: int, right: int) -> ListNode:
    """
    Reverse linked list from position left to right.

    Time Complexity: O(n)
    Space Complexity: O(1)
    """
    if not head or left == right:
        return head

    # Create dummy node to handle edge cases
    dummy = ListNode(0)
    dummy.next = head
    prev = dummy

    # Step 1: Move to node before left position
    for _ in range(left - 1):
        prev = prev.next

    # Step 2: Reverse sublist from left to right
    current = prev.next  # First node to reverse

    for _ in range(right - left):
        # Move current.next to front of reversed section
        next_node = current.next
        current.next = next_node.next
        next_node.next = prev.next
        prev.next = next_node

    return dummy.next

# Test case: Reverse positions 2-4 in 1→2→3→4→5
# Result: 1→4→3→2→5

Visualization for reverseBetween([1,2,3,4,5], 2, 4)

Initial: 1  2  3  4  5
                     
            left    right

After step 1 (prev at 1):
dummy  1  2  3  4  5
           
      prev current

Iteration 1: Move 3 to front
1  3  2  4  5

Iteration 2: Move 4 to front
1  4  3  2  5

Final: 1  4  3  2  5

Common Mistakes

  • ❌ Losing reference to next node before reversing pointer
  • ❌ Forgetting to return prev instead of head
  • ❌ Not handling single-node or empty list edge cases
  • ❌ Off-by-one errors in position counting

7. Binary Search Pattern

Core Principle

Binary Search eliminates half of the search space in each iteration by leveraging sorted data. This reduces linear O(n)O(n) search to logarithmic O(logn)O(log n).

Core formula:
mid=left+rightleft2text{mid} = text{left} + frac{text{right} – text{left}}{2}

Using left + (right - left) // 2 instead of (left + right) // 2 prevents integer overflow.

When to use:
– Searching in sorted arrays
– Finding boundaries (first/last occurrence)
– Minimizing/maximizing with constraint checking
– Matrix search (row/column sorted)

Classic Problem: Binary Search in Sorted Array

Problem: Find target value in sorted array. Return index or -1 if not found.

Template Structure

def binarySearch(nums: List[int], target: int) -> int:
    """
    Standard binary search implementation.

    Time Complexity: O(log n) - halve search space each iteration
    Space Complexity: O(1) - only pointer variables
    """
    left = 0
    right = len(nums) - 1

    while left <= right:
        # Calculate middle index (prevent overflow)
        mid = left + (right - left) // 2

        if nums[mid] == target:
            return mid  # Found target
        elif nums[mid] < target:
            left = mid + 1  # Search right half
        else:
            right = mid - 1  # Search left half

    return -1  # Target not found

# Test cases
print(binarySearch([1, 3, 5, 7, 9, 11], 7))   # 3
print(binarySearch([1, 3, 5, 7, 9, 11], 4))   # -1

Execution Trace

Input: nums = [1, 3, 5, 7, 9, 11], target = 7

Iteration 1:
  left=0, right=5, mid=2
  nums[2]=5 < 7  search right half
  left=3

Iteration 2:
  left=3, right=5, mid=4
  nums[4]=9 > 7  search left half
  right=3

Iteration 3:
  left=3, right=3, mid=3
  nums[3]=7 = 7  FOUND!
  Return 3

Advanced: Find First and Last Position

Problem: Find starting and ending position of target in sorted array with duplicates.

def searchRange(nums: List[int], target: int) -> List[int]:
    """
    Find first and last occurrence of target.

    Time Complexity: O(log n) - two binary searches
    Space Complexity: O(1)
    """
    def findBoundary(nums, target, findLeft):
        left, right = 0, len(nums) - 1
        boundary = -1

        while left <= right:
            mid = left + (right - left) // 2

            if nums[mid] == target:
                boundary = mid  # Store potential boundary

                # Continue searching for extreme boundary
                if findLeft:
                    right = mid - 1  # Search left for first occurrence
                else:
                    left = mid + 1   # Search right for last occurrence
            elif nums[mid] < target:
                left = mid + 1
            else:
                right = mid - 1

        return boundary

    first = findBoundary(nums, target, True)
    last = findBoundary(nums, target, False)

    return [first, last]

# Test cases
print(searchRange([5,7,7,8,8,10], 8))  # [3, 4]
print(searchRange([5,7,7,8,8,10], 6))  # [-1, -1]
print(searchRange([], 0))              # [-1, -1]

Key Insight: Finding Boundaries

Goal Condition When Found Next Action
First occurrence nums[mid] == target Continue searching left (right = mid - 1)
Last occurrence nums[mid] == target Continue searching right (left = mid + 1)

Binary Search on Answer Space

Some problems don’t search in arrays but search for optimal answers:

def minEatingSpeed(piles: List[int], h: int) -> int:
    """
    Koko Eating Bananas: Find minimum eating speed to finish within h hours.

    Time Complexity: O(n log m) where m = max(piles)
    Space Complexity: O(1)
    """
    def canFinish(speed):
        """Check if Koko can finish all piles at given speed."""
        hours = 0
        for pile in piles:
            # Ceiling division: (pile + speed - 1) // speed gives ceil(pile/speed)
            hours += (pile + speed - 1) // speed
        return hours <= h

    # Binary search on speed range [1, max(piles)]
    left, right = 1, max(piles)
    result = right

    while left <= right:
        mid = left + (right - left) // 2

        if canFinish(mid):
            result = mid        # Valid speed, try slower
            right = mid - 1
        else:
            left = mid + 1      # Too slow, try faster

    return result

# Test case
print(minEatingSpeed([3,6,7,11], 8))  # 4
print(minEatingSpeed([30,11,23,4,20], 5))  # 30

Problem Analysis

Intuition: If Koko can finish at speed kk, she can also finish at any speed &gt; k. This monotonic property enables binary search on the answer space.

Search space: [1,max(piles)][1, max(text{piles})]

Objective: Find minimum kk where canFinish(k) == True

Binary Search Variants Comparison

Variant Loop Condition Use Case
left <= right Exact match search Finding specific element
left < right Finding boundaries Minimization/maximization problems
left + 1 < right Avoid infinite loop Complex condition logic

Common Pitfalls

  • ❌ Using (left + right) // 2 causing overflow (use left + (right - left) // 2)
  • ❌ Wrong loop termination condition (<= vs <)
  • ❌ Not updating left/right correctly (off-by-one errors)
  • ❌ Forgetting to handle empty array edge case

Pro Tip: Always trace through examples with arrays of size 1, 2, and 3 to catch boundary bugs.


Pattern Selection Guide

Choosing the right pattern is crucial. Use this decision tree:

By Data Structure

Data Structure Consider These Patterns
Array (unsorted) Sliding Window, Two Pointers (after sorting)
Array (sorted) Binary Search, Two Pointers, Merge Intervals
Array (range [1,n]) Cyclic Sort
Linked List Fast & Slow Pointers, In-Place Reversal
Intervals Merge Intervals

By Problem Type

Problem Contains Primary Pattern
“contiguous subarray/substring” Sliding Window
“find pair/triplet with sum” Two Pointers
“detect cycle” Fast & Slow Pointers
“sorted array” + “search” Binary Search
“merge/overlap intervals” Merge Intervals
“missing/duplicate in [1,n]” Cyclic Sort
“reverse linked list” In-Place Reversal

By Complexity Requirement

Constraint Suitable Patterns
O(n)O(n) time Two Pointers, Sliding Window, Cyclic Sort
O(logn)O(log n) time Binary Search
O(1)O(1) space Two Pointers, Cyclic Sort, In-Place Reversal

Practice Strategy

Difficulty Progression

Easy Level (Build Foundation)

  • Focus on understanding core mechanics
  • Implement pattern templates from scratch
  • Time yourself: aim for 15-20 minutes per problem

Recommended problems:
– Two Pointers: Valid Palindrome, Remove Duplicates
– Sliding Window: Maximum Average Subarray
– Fast & Slow: Linked List Cycle
– Binary Search: Binary Search, Search Insert Position

Medium Level (Master Variations)

  • Combine multiple patterns
  • Handle edge cases independently
  • Aim for 25-30 minutes per problem

Recommended problems:
– Two Pointers: 3Sum, Container With Most Water
– Sliding Window: Longest Substring Without Repeating Characters
– Merge Intervals: Merge Intervals, Insert Interval
– Binary Search: Search in Rotated Sorted Array

Hard Level (Optimize & Innovate)

  • Recognize patterns in disguised problems
  • Optimize further (time/space tradeoffs)
  • Aim for 35-45 minutes

Recommended problems:
– Sliding Window: Minimum Window Substring
– Merge Intervals: Employee Free Time
– Binary Search: Median of Two Sorted Arrays

Debugging Checklist

When your solution fails:

  1. Edge cases:
    – [ ] Empty input ([], None)
    – [ ] Single element
    – [ ] All elements identical
    – [ ] Maximum constraints (e.g., array size = 10^5)

  2. Pattern-specific checks:
    Two Pointers: Check pointer initialization and movement logic
    Sliding Window: Verify window expansion/contraction conditions
    Binary Search: Trace mid calculation and boundary updates
    Linked List: Check None pointer handling

  3. Complexity verification:
    – Trace through with small examples
    – Count nested loops (usually O(n2)O(n^2) is a red flag)

Interview Communication Tips

  1. State the pattern: “This looks like a two pointers problem because…”
  2. Explain complexity upfront: “Brute force is O(n2)O(n^2), but using sliding window achieves O(n)O(n)
  3. Walk through example: Trace your algorithm step-by-step
  4. Mention tradeoffs: “We gain time efficiency but use extra space for the hashmap”

Conclusion

Mastering these 7 essential patterns transforms coding interviews from intimidating challenges into systematic problem-solving exercises. Each pattern represents a fundamental algorithmic paradigm:

  1. Two Pointers — Efficient pair/triplet searching with O(n)O(n) time
  2. Sliding Window — Optimized subarray/substring problems
  3. Fast & Slow Pointers — Cycle detection and list manipulation
  4. Merge Intervals — Handling overlapping ranges elegantly
  5. Cyclic Sort — Leveraging index relationships for O(1)O(1) space
  6. In-Place Reversal — Memory-efficient linked list restructuring
  7. Binary Search — Logarithmic search on sorted data and answer spaces

Key takeaways:

  • Pattern recognition beats memorization: Understanding when and why to apply each pattern is more valuable than memorizing solutions
  • Complexity matters: Always analyze time and space tradeoffs—interviewers evaluate algorithmic thinking
  • Practice deliberately: Start with easy problems, master variations, then tackle hard problems
  • Communication is crucial: Explain your reasoning, discuss alternatives, and think aloud

Next steps:

  1. Implement each pattern’s template from scratch
  2. Solve 5-10 problems per pattern (vary difficulty)
  3. Time yourself and review solutions afterward
  4. Revisit patterns weekly to maintain proficiency

With consistent practice, these patterns become intuitive tools in your problem-solving toolkit. You’ll spend less time figuring out how to solve problems and more time optimizing solutions and handling edge cases—exactly what interviewers want to see.

Happy coding, and good luck with your interviews! 🚀

Did you find this helpful?

Your support keeps this blog running and ad-free content coming.

☕ Buy me a coffee
TODAY 479 | TOTAL 113,755