Skip to main content

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
info

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)

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)

tip

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:

  1. Is the data sorted? → Consider binary search or two pointers
  2. Do I need to find subarrays/substrings? → Consider sliding window
  3. Is it a tree/graph problem? → Consider DFS or BFS
  4. Do I need optimal solution with subproblems? → Consider DP
  5. Do I need all possible combinations? → Consider backtracking
  6. Is there a cycle to detect? → Consider fast/slow pointers
  7. Do I need top/bottom K elements? → Consider heap
  8. Are there overlapping ranges? → Consider merge intervals

Practice strategy

Here's how I recommend practicing:

  1. Start with easy problems for each pattern
  2. Solve 5-10 problems per pattern to internalize it
  3. Time yourself to simulate interview conditions
  4. Explain your solution out loud to practice communication
  5. Review others' solutions to learn alternative approaches
tip

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! 💻