Why Patterns Matter More Than Problems
The biggest mistake developers make in DSA preparation is grinding LeetCode randomly. With over 3,000 problems on the platform, brute-force practice is impossibly inefficient. The secret that consistently separates candidates who clear interviews at Google, Amazon, Flipkart, and Razorpay from those who don’t is pattern recognition. There are roughly 10-15 core patterns that cover 90% of coding interview questions. Learn the pattern, and you can solve any problem that fits it.
This guide covers the 10 patterns that appear most frequently in Indian tech interviews, with Python implementations you can adapt and apply immediately.
1. Two Pointers
Use when working with sorted arrays or when you need to find pairs that satisfy a condition. Two pointers move toward each other or in the same direction, reducing O(n^2) brute force to O(n).
def two_sum_sorted(nums: list[int], target: int) -> list[int]:
"""Find two numbers in a sorted array that sum to 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]
def remove_duplicates(nums: list[int]) -> int:
"""Remove duplicates in-place from sorted array. Returns new length."""
if not nums:
return 0
write = 1
for read in range(1, len(nums)):
if nums[read] != nums[read - 1]:
nums[write] = nums[read]
write += 1
return write
Key problems: Container With Most Water, 3Sum, Trapping Rain Water, Remove Duplicates.
2. Sliding Window
Use for problems involving contiguous subarrays or substrings. Maintain a window that expands or shrinks based on conditions.
def max_sum_subarray(nums: list[int], k: int) -> int:
"""Maximum sum of any subarray of size k (fixed window)."""
window_sum = sum(nums[:k])
max_sum = window_sum
for i in range(k, len(nums)):
window_sum += nums[i] - nums[i - k]
max_sum = max(max_sum, window_sum)
return max_sum
def longest_substring_without_repeat(s: str) -> int:
"""Longest substring without repeating characters (variable window)."""
char_index = {}
max_length = 0
left = 0
for right, char in enumerate(s):
if char in char_index and char_index[char] >= left:
left = char_index[char] + 1
char_index[char] = right
max_length = max(max_length, right - left + 1)
return max_length
Key problems: Minimum Window Substring, Longest Repeating Character Replacement, Maximum Average Subarray.
3. Binary Search
Not just for sorted arrays. Use binary search whenever you can define a monotonic condition that partitions the search space.
def search_rotated(nums: list[int], target: int) -> int:
"""Search in a rotated sorted array. O(log n)."""
left, right = 0, len(nums) - 1
while left <= right:
mid = (left + right) // 2
if nums[mid] == target:
return mid
# Left half is sorted
if nums[left] <= nums[mid]:
if nums[left] <= target < nums[mid]:
right = mid - 1
else:
left = mid + 1
# Right half is sorted
else:
if nums[mid] < target <= nums[right]:
left = mid + 1
else:
right = mid - 1
return -1
def find_peak_element(nums: list[int]) -> int:
"""Find a peak element index. O(log n)."""
left, right = 0, len(nums) - 1
while left < right:
mid = (left + right) // 2
if nums[mid] < nums[mid + 1]:
left = mid + 1
else:
right = mid
return left
Key problems: Koko Eating Bananas, Find Minimum in Rotated Array, Median of Two Sorted Arrays.
4. BFS and DFS (Graph/Tree Traversal)
BFS explores level by level (use a queue). DFS goes deep first (use recursion or a stack). These are the backbone of tree and graph problems.
from collections import deque
def level_order_traversal(root) -> list[list[int]]:
"""BFS level-order traversal of a binary tree."""
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
def num_islands(grid: list[list[str]]) -> int:
"""Count islands using DFS. Classic graph problem."""
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" # Mark visited
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
Key problems: Word Ladder (BFS), Clone Graph, Course Schedule, Rotting Oranges.
5. Dynamic Programming
Use when a problem has overlapping subproblems and optimal substructure. Start with recursion, add memoization, then convert to tabulation if needed.
def longest_common_subsequence(text1: str, text2: str) -> int:
"""Classic DP: LCS of two strings. O(m*n) time and space."""
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]
def coin_change(coins: list[int], amount: int) -> int:
"""Minimum coins to make amount. Bottom-up DP."""
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
Key problems: 0/1 Knapsack, Longest Increasing Subsequence, Edit Distance, House Robber.
6. Backtracking
Systematic exploration of all possibilities, pruning branches that can’t lead to a solution.
def combination_sum(candidates: list[int], target: int) -> list[list[int]]:
"""Find all unique combinations that sum to target."""
result = []
def backtrack(start, current, remaining):
if remaining == 0:
result.append(current[:])
return
if remaining < 0:
return
for i in range(start, len(candidates)):
current.append(candidates[i])
backtrack(i, current, remaining - candidates[i])
current.pop() # Undo choice
backtrack(0, [], target)
return result
Key problems: N-Queens, Sudoku Solver, Permutations, Word Search.
7. Hash Map / Frequency Counting
The most versatile pattern. Use hash maps to achieve O(1) lookups and reduce time complexity.
def group_anagrams(strs: list[str]) -> list[list[str]]:
"""Group strings that are anagrams of each other."""
from collections import defaultdict
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: list[int], k: int) -> int:
"""Count subarrays with sum equal to k using prefix sum + hash map."""
count = 0
prefix_sum = 0
prefix_counts = {0: 1}
for num in nums:
prefix_sum += num
count += prefix_counts.get(prefix_sum - k, 0)
prefix_counts[prefix_sum] = prefix_counts.get(prefix_sum, 0) + 1
return count
8. Monotonic Stack
Use when you need to find the next greater or smaller element for each position in an array.
def next_greater_element(nums: list[int]) -> list[int]:
"""For each element, find the next greater element to its right."""
n = len(nums)
result = [-1] * n
stack = [] # Stores indices
for i in range(n):
while stack and nums[i] > nums[stack[-1]]:
result[stack.pop()] = nums[i]
stack.append(i)
return result
9. Merge Intervals
Sort intervals by start time, then merge overlapping ones.
def merge_intervals(intervals: list[list[int]]) -> list[list[int]]:
"""Merge all overlapping 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
10. Topological Sort
Use for dependency resolution problems where you need to find a valid ordering.
def course_schedule(num_courses: int, prerequisites: list[list[int]]) -> list[int]:
"""Return a valid course order (topological sort using Kahn's algorithm)."""
from collections import deque, defaultdict
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 []
Conclusion
These 10 patterns cover the vast majority of DSA questions you’ll encounter in interviews at Indian and global tech companies. The strategy is simple: learn each pattern deeply, solve 3-5 problems per pattern, and practice identifying which pattern fits a new problem before writing code. Pattern recognition is the skill that separates candidates who solve problems in 15 minutes from those who struggle for an hour. Build a spreadsheet mapping problems to patterns, revisit weak areas weekly, and you’ll walk into any coding interview with confidence.