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 brute force solutions to 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 () would still give you an efficient solution.
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 .
Naive vs. Optimized Approach
| Approach | Time Complexity | Description |
|---|---|---|
| Brute Force | Recalculate sum for each window | |
| Sliding Window | 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 :
- Slow pointer moves step per iteration
- Fast pointer moves steps per iteration
- Speed difference: step per iteration
If a cycle exists with length , once both pointers are inside the cycle, the fast pointer gains step on the slow pointer each iteration. The relative distance between them decreases by each step, so they must meet within at most iterations after both enter the cycle.
where 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.
Related Problems
| 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:
2. Merge overlapping intervals in single pass:
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 or . The idea: place each number at its “correct” index position.
Key insight: For array of size with numbers to , number should be at index .
When to use:
– Finding missing/duplicate numbers
– Array elements in known range
– space constraint
Classic Problem: Find Missing Number
Problem: Given array containing distinct numbers in range , find the missing number.
Algorithm Approach Comparison
| Approach | Time | Space | Method |
|---|---|---|---|
| Sorting | Sort and scan for gap | ||
| Hash Set | Store all, check 0 to n | ||
| Sum Formula | |||
| Cyclic Sort | 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 or
– Problem asks for missing/duplicate/smallest missing positive
– Need 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:
- prev: Previously processed node
- current: Node being processed
- 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
previnstead ofhead - ❌ 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 search to logarithmic .
Core formula:
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 , she can also finish at any speed > k. This monotonic property enables binary search on the answer space.
Search space:
Objective: Find minimum 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) // 2causing overflow (useleft + (right - left) // 2) - ❌ Wrong loop termination condition (
<=vs<) - ❌ Not updating
left/rightcorrectly (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 |
|---|---|
| time | Two Pointers, Sliding Window, Cyclic Sort |
| time | Binary Search |
| 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:
-
Edge cases:
– [ ] Empty input ([],None)
– [ ] Single element
– [ ] All elements identical
– [ ] Maximum constraints (e.g., array size = 10^5) -
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 -
Complexity verification:
– Trace through with small examples
– Count nested loops (usually is a red flag)
Interview Communication Tips
- State the pattern: “This looks like a two pointers problem because…”
- Explain complexity upfront: “Brute force is , but using sliding window achieves “
- Walk through example: Trace your algorithm step-by-step
- 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:
- Two Pointers — Efficient pair/triplet searching with time
- Sliding Window — Optimized subarray/substring problems
- Fast & Slow Pointers — Cycle detection and list manipulation
- Merge Intervals — Handling overlapping ranges elegantly
- Cyclic Sort — Leveraging index relationships for space
- In-Place Reversal — Memory-efficient linked list restructuring
- 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:
- Implement each pattern’s template from scratch
- Solve 5-10 problems per pattern (vary difficulty)
- Time yourself and review solutions afterward
- 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 coffeeMost Popular Posts
- Custom Metaclass in Python: 43% Faster Validation (12,797 views)
- Python match-case: 7 Patterns That Beat if-elif Chains (947 views)
- YOLOv8 INT8 Quantization: 4x Faster on Jetson Orin (769 views)
- yfinance Alternatives 2026: 7 Free APIs Compared (658 views)
- PaddleOCR vs EasyOCR vs Tesseract: Why PaddleOCR Is Slower (552 views)