Understanding algorithm patterns
After solving hundreds of algorithmic problems on LeetCode and HackerRank, I've realized that most coding interview questions follow recognizable patterns. Once you understand these patterns, you can approach new problems with confidence, even if you've never seen that exact problem before.
In this article, I'll break down the most common algorithm patterns that appear in technical interviews and real-world programming.
Why patterns matter
Instead of memorizing thousands of individual problems, learning patterns allows you to:
- Identify problem types quickly
- Apply known solutions to new problems
- Save time during interviews
- Build a mental framework for problem-solving
Think of patterns as tools in your toolbox. The more tools you have, the better equipped you are to solve any problem.
Core algorithm patterns
1. Two Pointers
When to use: Problems involving arrays or linked lists where you need to find pairs, triplets, or subarrays.
How it works: Use two pointers that move through the data structure, typically from opposite ends or at different speeds.
Common problems:
- Finding pairs that sum to a target
- Removing duplicates from sorted array
- Container with most water
- Valid palindrome
Example: Two Sum II (sorted array)
def twoSum(numbers, target):
left, right = 0, len(numbers) - 1
while left < right:
current_sum = numbers[left] + numbers[right]
if current_sum == target:
return [left + 1, right + 1]
elif current_sum < target:
left += 1
else:
right -= 1
return []
Time Complexity: O(n) Space Complexity: O(1)
2. Sliding Window
When to use: Problems involving contiguous subarrays or substrings with specific properties.
How it works: Maintain a "window" that slides over the array/string, expanding or contracting based on conditions.
Common problems:
- Maximum sum subarray of size K
- Longest substring without repeating characters
- Minimum window substring
- Find all anagrams in a string
Example: Longest substring without repeating characters
def lengthOfLongestSubstring(s):
char_set = set()
left = 0
max_length = 0
for right in range(len(s)):
# Shrink window until no duplicates
while s[right] in char_set:
char_set.remove(s[left])
left += 1
char_set.add(s[right])
max_length = max(max_length, right - left + 1)
return max_length
Time Complexity: O(n) Space Complexity: O(min(n, m)) where m is the character set size
3. Fast and Slow Pointers
When to use: Linked list problems involving cycle detection or finding the middle element.
How it works: Use two pointers moving at different speeds (typically 1x and 2x).
Common problems:
- Detect cycle in linked list
- Find the middle of linked list
- Happy number problem
- Palindrome linked list
Example: Linked list cycle detection
def hasCycle(head):
if not head:
return False
slow = fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
if slow == fast:
return True
return False
Time Complexity: O(n) Space Complexity: O(1)
4. Merge Intervals
When to use: Problems dealing with overlapping intervals or ranges.
How it works: Sort intervals, then merge overlapping ones.
Common problems:
- Merge overlapping intervals
- Insert interval
- Meeting rooms
- Interval list intersections
Example: Merge intervals
def merge(intervals):
if not intervals:
return []
# Sort by start time
intervals.sort(key=lambda x: x[0])
merged = [intervals[0]]
for current in intervals[1:]:
last = merged[-1]
if current[0] <= last[1]:
# Overlapping - merge
last[1] = max(last[1], current[1])
else:
# Non-overlapping - add new interval
merged.append(current)
return merged
Time Complexity: O(n log n) Space Complexity: O(n)
5. Binary Search
When to use: Searching in sorted arrays or whenever you can eliminate half the search space.
How it works: Repeatedly divide the search space in half.
Common problems:
- Search in rotated sorted array
- Find peak element
- Search in 2D matrix
- Find minimum in rotated sorted array
Example: Classic binary search
def binarySearch(nums, target):
left, right = 0, len(nums) - 1
while left <= right:
mid = left + (right - left) // 2
if nums[mid] == target:
return mid
elif nums[mid] < target:
left = mid + 1
else:
right = mid - 1
return -1
Time Complexity: O(log n) Space Complexity: O(1)
Binary search isn't just for finding elements. It's great for "search space" problems where you're looking for the minimum/maximum value that satisfies a condition.
6. Depth-First Search (DFS)
When to use: Tree and graph traversal, exploring all paths, backtracking problems.
How it works: Explore as far as possible along each branch before backtracking.
Common problems:
- Tree traversals (preorder, inorder, postorder)
- Path sum problems
- Number of islands
- Clone graph
Example: Tree DFS (recursive)
def maxDepth(root):
if not root:
return 0
left_depth = maxDepth(root.left)
right_depth = maxDepth(root.right)
return 1 + max(left_depth, right_depth)
Time Complexity: O(n) Space Complexity: O(h) where h is tree height
7. Breadth-First Search (BFS)
When to use: Level-order traversal, shortest path in unweighted graphs.
How it works: Explore all neighbors at the current depth before moving deeper.
Common problems:
- Level order traversal
- Shortest path in binary matrix
- Binary tree right side view
- Rotting oranges
Example: Level order traversal
from collections import deque
def levelOrder(root):
if not root:
return []
result = []
queue = deque([root])
while queue:
level_size = len(queue)
current_level = []
for _ in range(level_size):
node = queue.popleft()
current_level.append(node.val)
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
result.append(current_level)
return result
Time Complexity: O(n) Space Complexity: O(w) where w is max width of tree
8. Dynamic Programming
When to use: Optimization problems with overlapping subproblems and optimal substructure.
How it works: Break problems into smaller subproblems, store solutions to avoid recomputation.
Common problems:
- Fibonacci sequence
- Longest common subsequence
- Coin change
- House robber
- Climbing stairs
Example: Coin change
def coinChange(coins, amount):
dp = [float('inf')] * (amount + 1)
dp[0] = 0
for coin in coins:
for x in range(coin, amount + 1):
dp[x] = min(dp[x], dp[x - coin] + 1)
return dp[amount] if dp[amount] != float('inf') else -1
Time Complexity: O(n × m) where n is amount, m is number of coins Space Complexity: O(n)
9. Backtracking
When to use: Problems requiring exploration of all possible solutions (combinations, permutations, subsets).
How it works: Build solutions incrementally, abandoning candidates that cannot lead to valid solutions.
Common problems:
- Generate parentheses
- Subsets and permutations
- N-Queens
- Sudoku solver
- Word search
Example: Generate all subsets
def subsets(nums):
result = []
def backtrack(start, current):
result.append(current[:])
for i in range(start, len(nums)):
current.append(nums[i])
backtrack(i + 1, current)
current.pop()
backtrack(0, [])
return result
Time Complexity: O(2^n) Space Complexity: O(n)
10. Top K Elements
When to use: Problems asking for K largest/smallest elements.
How it works: Use a heap (priority queue) to efficiently track top K elements.
Common problems:
- Kth largest element
- Top K frequent elements
- K closest points to origin
- Find median from data stream
Example: K closest points to origin
import heapq
def kClosest(points, k):
# Use max heap to keep k smallest distances
heap = []
for x, y in points:
dist = -(x*x + y*y) # Negative for max heap
if len(heap) < k:
heapq.heappush(heap, (dist, [x, y]))
else:
heapq.heappushpop(heap, (dist, [x, y]))
return [point for (_, point) in heap]
Time Complexity: O(n log k) Space Complexity: O(k)
Pattern recognition tips
When you see a problem, ask yourself:
- Is the data sorted? → Consider binary search or two pointers
- Do I need to find subarrays/substrings? → Consider sliding window
- Is it a tree/graph problem? → Consider DFS or BFS
- Do I need optimal solution with subproblems? → Consider DP
- Do I need all possible combinations? → Consider backtracking
- Is there a cycle to detect? → Consider fast/slow pointers
- Do I need top/bottom K elements? → Consider heap
- Are there overlapping ranges? → Consider merge intervals
Practice strategy
Here's how I recommend practicing:
- Start with easy problems for each pattern
- Solve 5-10 problems per pattern to internalize it
- Time yourself to simulate interview conditions
- Explain your solution out loud to practice communication
- Review others' solutions to learn alternative approaches
Don't just solve problems—understand WHY a particular pattern works for that problem. This builds intuition.
Conclusion
Mastering these algorithm patterns is like learning the grammar of coding interviews. Once you know the patterns, you can:
- Solve new problems faster
- Write more efficient code
- Communicate your approach clearly
- Feel confident in interviews
Remember, the goal isn't to memorize solutions but to recognize patterns and understand when and why to apply them. With practice, pattern recognition becomes second nature.
Happy coding! 💻