Knowing 10 Patterns Will Solve 80% of Interview Problems. Memorizing 500 Won’t.
Here’s something nobody at my coaching center in Hyderabad told me in 2019: grinding LeetCode problems without a system is like memorizing an entire dictionary to learn English. Sure, you’ll recognize words. But you still won’t be able to hold a conversation.
I learned that the hard way. Three months before my first real interview — a Flipkart SDE-1 slot I’d been dreaming about since second year of college — I was solving problems at random. Easy, medium, hard, whatever the daily challenge was. Some days I’d crack a problem in twenty minutes and feel invincible. Other days I’d stare at a medium-difficulty question for two hours and give up. My confidence swung like a pendulum, and I couldn’t figure out why I could solve one problem but completely blank on another that looked almost identical.
Then a senior from my batch who’d landed at Google Bangalore told me something that changed everything. “Stop solving problems,” he said. “Start learning patterns.”
Patterns. Not individual problems, but the recurring structures underneath them. Once you see it, you can’t unsee it: maybe 80% of the DSA questions asked in Indian tech interviews — at TCS, Infosys, Flipkart, Razorpay, Google India, Amazon Hyderabad, everywhere — boil down to roughly ten core patterns. Master those ten, and a problem you’ve never seen before suddenly looks like a variation of something you already know.
Let me walk you through each one the way I actually encountered them — in the pressure cooker of real interview prep, with real problems, real confusion, and real breakthroughs.
1. Two Pointers — Where It All Clicks
My first “pattern moment” happened with two pointers. I’d been trying to solve a pair-sum problem on a sorted array, and my gut instinct was nested loops. Check every pair. O(n^2) time. It worked, but it was slow, and I knew interviewers at product companies wouldn’t accept it.
A friend showed me the trick: start one pointer at the beginning and one at the end. If the sum is too small, move the left pointer right. Too big? Move the right pointer left. You converge toward the answer in O(n) time, and the logic is almost embarrassingly simple once you see it.
That was the moment algorithms stopped feeling like magic to me. Two pointers is probably the gentlest on-ramp into pattern-based thinking. You’ll see it in sorted array problems, in “find a pair” problems, even in removing duplicates in-place.
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
Look at remove_duplicates — it’s still two pointers, just both moving in the same direction. One reads ahead, one writes behind. When you’ve internalized this pattern, Container With Most Water, 3Sum, even Trapping Rain Water start to feel like cousins rather than strangers.
Interview appearances: Container With Most Water, 3Sum, Trapping Rain Water, Remove Duplicates from Sorted Array.
2. Sliding Window — The Subarray Superpower
Sometime around November 2019, I hit a wall. Every problem involving subarrays or substrings seemed to demand a different approach, and I couldn’t find the common thread. Then I encountered the “maximum sum subarray of size k” problem, and someone in a Discord server wrote two words that rewired my brain: sliding window.
Imagine a physical window — a frame — sliding across an array. You maintain a running calculation inside the frame. When the window moves one step forward, you add the new element entering from the right and drop the element exiting from the left. No need to recalculate the entire window each time.
Fixed-size windows are straightforward. Variable-size windows, like finding the longest substring without repeating characters, require a bit more finesse — you expand the right boundary until a condition breaks, then contract the left boundary until you’re valid again. But it’s the same underlying idea.
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
I once got asked Minimum Window Substring in a Razorpay phone screen. Recognized the pattern within thirty seconds. Didn’t mean I wrote perfect code instantly, but knowing I needed a sliding window gave me a clear direction instead of fumbling with brute force.
Interview appearances: Minimum Window Substring, Longest Repeating Character Replacement, Maximum Average Subarray.
3. Binary Search — Not Just for Sorted Arrays
Everyone learns binary search in their second year data structures course. Split the array in half, check the middle, go left or right. Simple, right? For years I thought that was the whole story. Then I ran into “Search in Rotated Sorted Array” during a mock interview, and I realized I didn’t understand binary search at all.
Here’s the deeper truth: binary search works whenever you can define a monotonic condition that splits the search space into two halves — one where the condition is true and one where it’s false. Sorted arrays are just the most obvious case. You can binary search on the answer itself (like in Koko Eating Bananas, where you’re searching for the minimum eating speed), on peak elements, on rotated arrays.
Rotated sorted array tripped me up because half the array is sorted and half isn’t. But even in that chaos, one half is always sorted, and you can use that sorted half to decide which direction to go.
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
A friend who interviews candidates at Amazon Hyderabad told me they use binary search variants as a filter — it weeds out people who only memorized the textbook version. Worth spending extra time on this one, honestly.
Interview appearances: Koko Eating Bananas, Find Minimum in Rotated Array, Median of Two Sorted Arrays.
4. BFS and DFS — The Backbone of Everything Tree and Graph
Around January 2020, I started seeing tree and graph problems everywhere. Level-order traversal. Number of islands. Course prerequisites. At first they seemed unrelated. One’s a tree, one’s a grid, one’s a directed graph. But the traversal mechanics under the hood? Same two ideas, over and over: go wide (BFS) or go deep (DFS).
BFS uses a queue. Pop from the front, push neighbors to the back. You explore level by level, which makes it perfect for “shortest path in unweighted graph” problems and level-order tree traversals. DFS uses a stack (or recursion, which is just an implicit stack). You dive as deep as possible along one path before backtracking.
Number of Islands is maybe the most-asked graph problem in Indian tech interviews. I’ve seen it at TCS Digital, at startup interviews in Bangalore, at Infosys Power Programmer rounds. And it’s just DFS on a grid — mark cells as visited, count connected components.
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
Quick tip that saved me once: when an interviewer says “shortest path” and the graph is unweighted, reach for BFS. When they say “explore all possibilities” or “connected components,” DFS is usually your friend. Doesn’t always hold, but it’s a solid starting heuristic.
Interview appearances: Word Ladder (BFS), Clone Graph, Course Schedule, Rotting Oranges.
5. Dynamic Programming — The Pattern Everyone Fears (and Shouldn’t)
Let’s be honest. DP has a reputation problem. People hear “dynamic programming” and immediately picture impossible state transitions and five-dimensional arrays. I was terrified of DP for months.
What finally cracked it open for me was a shift in perspective. DP isn’t a single trick — it’s a way of thinking about problems that have two properties: overlapping subproblems (you solve the same smaller problem multiple times) and optimal substructure (the best solution to the big problem can be built from best solutions to smaller problems).
My approach, which I’d probably recommend to anyone starting out: write the recursive solution first. Don’t optimize. Just get the recursion right. Then add memoization — cache the results of subproblems so you don’t recompute them. Once that works, you can convert to bottom-up tabulation if the interviewer asks for it or if you want to save stack space.
Longest Common Subsequence was my gateway DP problem. Two strings, find the longest sequence of characters that appears in both (not necessarily contiguous). Classic 2D DP.
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
Coin Change is another favourite at Indian product companies. I’ve been asked it (or a variation of it) three separate times across different interviews. Flipkart, a fintech startup in Mumbai, and a mock interview that felt harder than the real ones. Once you recognize the “minimum ways to reach a target” shape, the DP formulation falls into place.
Interview appearances: 0/1 Knapsack, Longest Increasing Subsequence, Edit Distance, House Robber.
6. Backtracking — When You Need to Explore Every Possibility
Backtracking showed up in my prep around February 2020, right before the world went sideways. I was working through permutations and combinations, and the code felt so… recursive. Like it was going down one path, hitting a dead end, backing up, and trying the next option.
Which is exactly what backtracking does. It’s systematic brute force with an escape hatch. You explore a path. If it can’t lead to a valid solution, you prune — you stop early, undo your last choice, and try the next option. Without pruning, it’s just brute force. With pruning, it becomes surprisingly efficient for many problems.
N-Queens is the textbook example, but Combination Sum is what I’d practice first. It’s cleaner and the pruning logic is more intuitive: if your remaining target goes negative, stop. Don’t keep adding numbers.
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
See that current.pop() at the end? That’s the backtrack. You made a choice (appended a candidate), explored what happens, and now you’re undoing the choice to try the next one. Every backtracking problem follows this template: choose, explore, un-choose.
Interview appearances: N-Queens, Sudoku Solver, Permutations, Word Search.
7. Hash Map / Frequency Counting — The Swiss Army Knife
If I had to pick one pattern that shows up most often across all interview types — service-based companies, product startups, FAANG — it’d be hash maps. And I’m not even exaggerating. Nearly every coding round I’ve done included at least one problem where the key insight was “use a hash map for O(1) lookups.”
Group Anagrams is a clean example. You need to group strings that are anagrams of each other. Without a hash map, you’d be comparing every pair of strings. With one, you sort each string to create a canonical key, and group by that key. O(n * k log k) where k is the max string length. Done.
But the real power move is the prefix sum + hash map combo. Subarray Sum Equals K is a problem I’d classify as “medium difficulty, high interview frequency.” You track prefix sums as you iterate, and at each step, you check if you’ve seen (current_prefix_sum – k) before. If yes, there’s a subarray summing to k. Brilliant, and not at all obvious the first time you see it.
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
A colleague who joined Infosys through their Specialist Programmer track told me he got asked Group Anagrams in his final technical round. Pretty standard for service companies that are upping their DSA expectations.
Interview appearances: Two Sum (unsorted), Top K Frequent Elements, Longest Consecutive Sequence, Subarray Sum Equals K.
8. Monotonic Stack — The “Next Greater Element” Pattern
Monotonic stacks confused me for a while. I understood stacks. I understood the word “monotonic.” But putting them together and seeing why it produces O(n) solutions for “next greater element” problems took a few attempts.
Here’s the intuition that finally landed for me. You iterate through the array. You maintain a stack of indices. At each new element, you pop everything from the stack that’s smaller than the current element — because you’ve just found their “next greater element.” Whatever remains on the stack is still waiting for its answer. Push the current index and move on.
Each element enters the stack once and leaves once. So it’s O(n) total, even though there’s a while loop inside the for loop. Amortized analysis in action, which is itself a good talking point if the interviewer asks about time complexity.
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
Monotonic stacks show up in a handful of high-value problems: Daily Temperatures, Largest Rectangle in Histogram, Stock Span. Not the most frequently asked pattern overall, but when it shows up, it’s almost always the optimal approach, and knowing it gives you a massive edge over candidates who try to brute-force these.
Interview appearances: Daily Temperatures, Largest Rectangle in Histogram, Stock Span Problem, Next Greater Element variants.
9. Merge Intervals — Deceptively Simple, Surprisingly Common
I underestimated intervals problems for a long time. “Sort and merge? That can’t be a real interview question.” It absolutely can, and it absolutely is. Merge Intervals appears in coding rounds at companies ranging from TCS Digital to Amazon to early-stage startups. I’ve seen it or its close cousin (Insert Interval, Meeting Rooms) in at least four different interview pipelines.
Sort the intervals by their start time. Iterate through them. If the current interval overlaps with the last one you merged, extend the end. If it doesn’t, start a new merged interval. Clean, O(n log n) for the sort, O(n) for the merge pass.
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
What makes intervals tricky in interviews isn’t the algorithm — it’s the edge cases. What if two intervals share exactly one point? What if the input is empty? What about intervals that are completely nested inside another? Interviewers love probing these corners. Write the base algorithm, then walk through edge cases out loud. Shows maturity.
Interview appearances: Merge Intervals, Insert Interval, Meeting Rooms I and II, Non-overlapping Intervals.
10. Topological Sort — Dependency Problems Decoded
Last on the list, and maybe the most underrated. Topological sort isn’t glamorous. It doesn’t have the dramatic reveal of a DP state transition or the elegance of a two-pointer solution. But every time I see a problem that says “find a valid ordering” or “resolve dependencies,” I know exactly what to reach for.
Course Schedule is the canonical example. You have n courses and a list of prerequisites. Find an order to take all courses (or determine if it’s impossible because of circular dependencies). Kahn’s algorithm handles this beautifully: find all nodes with zero in-degree, process them, reduce the in-degree of their neighbors, repeat.
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 []
If len(order) != num_courses, there’s a cycle — some courses depend on each other in a loop, making it impossible. That check at the end is subtle but important. I’ve seen candidates forget it and lose points in otherwise strong interviews.
Beyond Course Schedule, topological sort appears in build systems (which files to compile first), task scheduling, and alien dictionary problems. Not asked as frequently as two pointers or hash maps, but when it shows up, candidates who don’t know the pattern tend to freeze.
Interview appearances: Course Schedule I and II, Alien Dictionary, Task Scheduler (variant), Build Order.
How to Actually Use These 10 Patterns
Knowing the patterns exists is step one. Turning that knowledge into interview performance? That’s the real work. Here’s what helped me, and what I’ve seen work for friends who cracked interviews at Flipkart, Google India, and startups across Bangalore and Pune in 2023 and 2024.
Build a pattern-problem spreadsheet. Every problem you solve, tag it with the pattern it uses. Within a few weeks, you’ll start noticing that your weak areas cluster around one or two patterns. Focus your energy there.
Solve 3-5 problems per pattern, not 30. Depth beats breadth. Solve one easy, two mediums, and one hard per pattern. By the time you’ve done fifty problems total (five per pattern, ten patterns), you’ll have stronger pattern recognition than someone who solved two hundred random problems.
Practice the recognition step separately. When you open a new problem, don’t write code for the first five minutes. Instead, ask yourself: which pattern fits? Is it a sorted array? Two pointers. Contiguous subarray? Sliding window. Dependencies? Topological sort. Overlapping subproblems? DP. Train the classification instinct and the code will follow.
Revisit weak patterns weekly. I kept a rotating schedule — Monday was DP day, Wednesday was graphs day. Spaced repetition works for algorithms just like it works for vocabulary.
Pattern recognition is, honestly, the skill that separates candidates who solve problems in fifteen minutes from those who struggle for an hour. It’s not about intelligence. It’s about having seen the shape before. And now you’ve seen all ten.
Which pattern do you keep getting stuck on?