The Only 25 Patterns You Need to Master DSA
Stop Memorizing Problems. Start Recognizing Patterns.

Search for a command to run...
Stop Memorizing Problems. Start Recognizing Patterns.

No comments yet. Be the first to comment.
Building a Scalable AI Startup Frontend Experience

Scaling Enterprise Reporting at Nokia

Modernizing Enterprise Microservices at Nokia

Building Scalable Analytics Infrastructure

There are thousands of coding problems, but there are far fewer ways of thinking about those problems.
That is the central idea behind pattern-based DSA preparation.
You do not need to memorize 500 solutions. You need to recognize that a new problem is often a variation of something you have already solved.
A string problem may secretly be a Sliding Window problem. A linked-list problem may secretly be a Fast & Slow Pointer problem. A scheduling problem may secretly be a Merge Intervals problem. A dependency problem may secretly be a Topological Sort problem. A shortest-path problem may secretly be a Dijkstra problem. And a seemingly impossible optimization problem may simply be Dynamic Programming.
This article organizes DSA into 25 reusable patterns. For each pattern, you get: why it exists, when to recognize it, pseudocode you can implement from, a worked example, a real-world application, and four hand-picked LeetCode problems (1 Easy, 2 Medium, 1 Hard — with a note wherever LeetCode itself doesn't offer a clean problem at that difficulty).
On the links below: every LeetCode link in this article was checked against LeetCode's own listed difficulty before being included, and every problem in this version is free — nothing requires a LeetCode subscription. A few patterns (Dijkstra, Topological Sort) don't have a clean, freely-accessible Easy problem on LeetCode; rather than mislabel a Medium as Easy, those sections say so explicitly instead.
Ask what the data looks like, then match it to a family of patterns:
1. Two Pointers
2. Fast & Slow Pointers
3. Sliding Window
4. Prefix Sum
5. HashMap
6. Stack
7. Queue
8. Sorting
9. Binary Search
10. Merge Intervals
11. Bitwise Operations
12. Heap / Priority Queue
13. Top K Elements
14. K-Way Merge
15. Trees & Tree Traversals
16. Depth First Search — DFS
17. Breadth First Search — BFS
18. Graphs
19. Greedy
20. Trie
21. Topological Sort
22. Dijkstra's Algorithm
23. Dynamic Programming — 1D
24. Dynamic Programming — 2D / Grid
25. Backtracking
Two Pointers is one of the most important techniques for arrays, strings, and linked structures. Instead of repeatedly examining every pair of elements, we maintain two positions and move them according to a rule. The pattern is particularly powerful when the data is sorted, when we need to compare elements from opposite ends, or when we need to maintain a relationship between two positions. Many O(n²) brute-force solutions become O(n) with the correct pointer movement.
left = 0
right = n - 1
while left < right:
evaluate(left, right)
if condition says move left:
left++
else:
right--
Find whether a sorted array contains two numbers whose sum equals target.
left = 0
right = n - 1
while left < right:
sum = nums[left] + nums[right]
if sum == target:
return true
if sum < target:
left++
else:
right--
return false
For nums = [1, 2, 4, 7, 11], target = 9:
Start with 1 + 11 = 12 → too large → move right. Then 1 + 7 = 8 → too small → move left. Then 2 + 7 = 9 → found.
Two pointers appear in comparing sorted datasets, merging streams, finding matching ranges, memory-efficient array processing, removing duplicates in-place, and partitioning data.
| Difficulty | Problem |
|---|---|
| Easy | Valid Palindrome |
| Medium | 3Sum |
| Medium | Container With Most Water |
| Hard | Trapping Rain Water |
Fast and Slow Pointers are designed for situations where you need to detect cycles, find the middle of a linked list, or reason about repeated states without storing every visited position. One pointer moves faster than the other. If a cycle exists, the faster pointer eventually catches the slower pointer. This eliminates extra memory and often turns a problem requiring a visited set into an O(1)-space solution.
slow = head
fast = head
while fast != null and fast.next != null:
slow = slow.next
fast = fast.next.next
if slow == fast:
return true
return false
For 1 → 2 → 3 → 4 → 5: slow moves one node at a time, fast moves two. When fast reaches the end, slow sits at 3 — the middle of the list.
Cycle detection, linked-list processing, detecting repeated states, circular buffers, and algorithms involving periodic sequences.
| Difficulty | Problem |
|---|---|
| Easy | Middle of the Linked List |
| Medium | Remove Nth Node From End of List |
| Medium | Find the Duplicate Number |
| Hard | Longest Duplicate Substring |
Note: Fast/Slow Pointer is predominantly an Easy/Medium pattern on LeetCode. The Hard problem above leans on a different, string-hashing style of repeated-state reasoning rather than the classic two-pointer cycle walk — don't expect every Hard linked-list problem to use the exact same pointer movement.
Sliding Window is the natural pattern whenever a problem asks about a contiguous portion of an array or string. Instead of recalculating every possible subarray, maintain a window [left, right] and update it incrementally. When the current window violates a constraint, move left. When it remains valid, expand right. This often converts O(n²) substring or subarray enumeration into O(n).
left = 0
for right in range(n):
add nums[right] to window
while window is invalid:
remove nums[left]
left++
update answer
Longest substring without repeating characters, s = "abcabcbb":
Expand: a, ab, abc. The next a creates a duplicate, so shrink from the left until the window is valid again. The maximum length is 3.
Network traffic monitoring, rate limiting, log analysis, time-series analytics, streaming data, and substring searches.
| Difficulty | Problem |
|---|---|
| Easy | Maximum Average Subarray I |
| Medium | Longest Substring Without Repeating Characters |
| Medium | Permutation in String |
| Hard | Minimum Window Substring |
Prefix Sum avoids repeated summation. If a problem repeatedly asks for the sum of a range, calculate cumulative information once and answer each range query in O(1). The deeper lesson: many problems become easier when you transform raw data into cumulative state. Prefix sums also combine extremely well with HashMaps, turning many subarray problems from quadratic into linear time.
prefix[0] = 0
for i in range(n):
prefix[i + 1] = prefix[i] + nums[i]
rangeSum(l, r) = prefix[r + 1] - prefix[l]
nums = [2, 4, 3, 5] → prefix = [0, 2, 6, 9, 14].
Sum from index 1 to 3 = prefix[4] - prefix[1] = 14 - 2 = 12.
Financial cumulative totals, image processing, range analytics, database aggregation, frequency counting, and time-series calculations.
| Difficulty | Problem |
|---|---|
| Easy | Running Sum of 1d Array |
| Medium | Subarray Sum Equals K |
| Medium | Product of Array Except Self |
| Hard | Maximum Sum of 3 Non-Overlapping Subarrays |
HashMaps are the answer whenever a problem asks you to remember something and retrieve it quickly. Instead of repeatedly scanning an array, store information in a hash table and achieve average O(1) lookup. HashMaps are powerful for frequency counting, complement searching, grouping, duplicate detection, and tracking previously seen states.
map = {}
for item in data:
if item exists in map:
use stored information
map[item] = updated information
Two Sum, nums = [2,7,11,15], target = 9. At 2, we need 7 → store 2 → index 0. At 7, we need 2 → it already exists → answer [0,1].
Caching, database indexing, frequency analysis, deduplication, lookup services, and session management.
| Difficulty | Problem |
|---|---|
| Easy | Two Sum |
| Medium | Group Anagrams |
| Medium | Longest Consecutive Sequence |
| Hard | Substring with Concatenation of All Words |
A Stack represents Last In, First Out behavior. It appears whenever the most recently opened, created, or processed item must be handled first — parentheses matching, expression evaluation, undo operations, browser history, and monotonic-stack problems all depend on stack-like reasoning. The important extension is the Monotonic Stack, which maintains increasing or decreasing values to efficiently answer "next greater/smaller" questions.
stack = []
for item in data:
while stack is not empty and condition(stack.top, item):
process(stack.pop())
stack.push(item)
Valid parentheses ([{}]): every closing bracket must match the most recently opened bracket, which is exactly what a stack tracks.
Undo/redo, browser navigation, compiler parsing, expression evaluation, call stacks, and next-event processing.
| Difficulty | Problem |
|---|---|
| Easy | Valid Parentheses |
| Medium | Min Stack |
| Medium | Daily Temperatures |
| Hard | Largest Rectangle in Histogram |
A Queue represents First In, First Out behavior — the natural structure for processing things in arrival order. Queues become especially important combined with BFS, where each layer of a graph or tree is processed before moving deeper. Deques (double-ended queues) allow insertion and removal from both ends and enable techniques such as Sliding Window Maximum.
queue.push(start)
while queue is not empty:
current = queue.popFront()
process(current)
for next in current.neighbors:
queue.push(next)
People entering a service queue A → B → C: A is processed first, then B, then C.
Job scheduling, print queues, request processing, BFS, message brokers, and customer service systems.
| Difficulty | Problem |
|---|---|
| Easy | Number of Recent Calls |
| Medium | Design Circular Queue |
| Medium | Rotting Oranges |
| Hard | Sliding Window Maximum |
Sorting transforms an unstructured problem into an ordered one. Once elements are sorted, duplicate detection, interval processing, two-pointer techniques, greedy decisions, and binary search often become possible. Understand not just how library sorting works, but the fundamental ideas behind Merge Sort, Quick Sort, Heap Sort, Counting Sort, and partitioning.
mergeSort(array):
if size <= 1:
return array
left = mergeSort(left half)
right = mergeSort(right half)
return merge(left, right)
[5, 2, 8, 1] → split into [5,2] [8,1] → sort into [2,5] [1,8] → merge into [1,2,5,8].
Database ORDER BY, ranking systems, report generation, scheduling, deduplication, and search preprocessing.
| Difficulty | Problem |
|---|---|
| Easy | Merge Sorted Array |
| Medium | Sort Colors |
| Medium | Sort an Array |
| Hard | Reverse Pairs |
Binary Search is not simply "searching a sorted array." Its real power is eliminating half the search space whenever a condition is monotonic. This means Binary Search applies to arrays, answer spaces, capacities, speeds, times, and many optimization problems. The key question: "Can I determine which half cannot contain the answer?"
left = low
right = high
while left <= right:
mid = left + (right - left) / 2
if found:
return mid
if target belongs to left half:
right = mid - 1
else:
left = mid + 1
return -1
nums = [1,3,5,7,9], target = 7. Check 5 → too small, discard left half. Check 7 → found.
Database indexes, filesystem searches, capacity planning, finding minimum feasible resources, and optimization problems.
| Difficulty | Problem |
|---|---|
| Easy | Binary Search |
| Medium | Search in Rotated Sorted Array |
| Medium | Koko Eating Bananas |
| Hard | Median of Two Sorted Arrays |
Whenever data represents ranges — meetings, bookings, reservations, time periods, memory regions — think in intervals. The key idea is usually to sort intervals by their starting point and then process them left to right. Once sorted, overlapping intervals become easy to identify because the next interval can only interact with the current merged interval.
sort intervals by start
result = []
for interval in intervals:
if result is empty or interval.start > result.last.end:
result.append(interval)
else:
result.last.end = max(result.last.end, interval.end)
return result
[1,3] [2,6] [8,10] [15,18] → first two overlap into [1,6].
Final result: [1,6], [8,10], [15,18].
Calendar scheduling, reservation systems, CPU scheduling, network maintenance windows, and resource allocation.
| Difficulty | Problem |
|---|---|
| Easy | Summary Ranges |
| Medium | Merge Intervals |
| Medium | Insert Interval |
| Hard | The Skyline Problem |
Note: LeetCode's cleanest Easy interval-merge problems (Meeting Rooms, Meeting Rooms II) are Premium-only. Summary Ranges is the free Easy substitute — it's a step removed from overlapping-interval merging (it groups consecutive integers into ranges rather than merging overlapping intervals), but it exercises the same left-pointer/right-pointer range-building instinct. The Skyline Problem is a free Hard problem that leans on the interval sweep-line idea, coordinating many overlapping ranges at once.
Bitwise operations manipulate the individual binary bits of integers. A small set of operations — AND, OR, XOR, NOT, left shift, right shift — solves a surprising number of problems. XOR is particularly important because x ^ x = 0 and x ^ 0 = x. Bit manipulation can produce extremely efficient solutions for parity, masks, subsets, state representation, and duplicate cancellation.
# Check bit
(n & (1 << k)) != 0
# Set bit
n = n | (1 << k)
# Clear bit
n = n & ~(1 << k)
# XOR matching pairs
answer = 0
for x in nums:
answer = answer ^ x
Find the single number in [4,1,2,1,2]: XOR everything. Since 1^1=0 and 2^2=0, the result is 4.
Permissions, feature flags, network masks, compression, cryptography primitives, and compact state representation.
| Difficulty | Problem |
|---|---|
| Easy | Single Number |
| Medium | Counting Bits |
| Medium | Maximum XOR of Two Numbers in an Array |
| Hard | Triples with Bitwise AND Equal To Zero |
A Heap is useful when you repeatedly need the smallest or largest element while the rest of the data stays unsorted. A sorted array requires expensive updates; a heap provides efficient insertion and removal of the current min/max. Priority Queues are essential for scheduling, Top K problems, Dijkstra, streaming medians, and resource allocation. Key question: "Do I repeatedly need the best current candidate?"
heap = empty
for value in data:
push(heap, value)
while heap is needed:
best = popMin(heap)
process(best)
Tasks A→priority 3, B→priority 1, C→priority 2. A priority queue returns them in order: B, C, A.
CPU scheduling, network packet prioritization, hospital triage, event simulation, task queues, and shortest-path algorithms.
| Difficulty | Problem |
|---|---|
| Easy | Last Stone Weight |
| Medium | Kth Largest Element in an Array |
| Medium | Top K Frequent Elements |
| Hard | Find Median from Data Stream |
Top K problems ask for only a small number of extreme elements rather than a fully sorted dataset. Sorting everything costs O(n log n), but if k is small, a heap can maintain only the best K candidates. The question is not "how do I sort the array?" but "how do I keep only the K elements that matter?"
minHeap = empty
for value in data:
push(minHeap, value)
if heap.size > k:
popMin(minHeap)
return minHeap
nums = [3,2,1,5,6,4], k = 2 → maintain only two largest values → result 5, 6.
Top search results, most popular products, highest salaries, most frequent events, recommendation systems, and ranking.
| Difficulty | Problem |
|---|---|
| Easy | Kth Largest Element in a Stream |
| Medium | Kth Largest Element in an Array |
| Medium | Top K Frequent Elements |
| Hard | Sliding Window Median |
K-Way Merge extends the classic two-array merge to many already-sorted sequences. The challenge is repeatedly identifying the smallest current element across K sources. A Min Heap makes this efficient: keep one candidate from each source, remove the smallest, then insert the next element from that same source.
heap = first element from each sorted list
while heap is not empty:
value, source = popMin(heap)
output value
if source has next element:
push(heap, next value from source)
A=[1,4,7] B=[2,5,8] C=[3,6,9]. Heap starts with 1,2,3. Remove 1, insert 4 → heap becomes 2,3,4. Continue until all elements are merged.
Merging database partitions, external sorting, log aggregation, merging sorted files, and distributed systems.
| Difficulty | Problem |
|---|---|
| Easy | Merge Sorted Array |
| Medium | Kth Smallest Element in a Sorted Matrix |
| Medium | Find K Pairs with Smallest Sums |
| Hard | Merge k Sorted Lists |
Trees introduce hierarchical data. The most important skill is systematically visiting every node. The three classic DFS traversals are Preorder, Inorder, and Postorder; BFS provides Level Order traversal. Once traversal is automatic, many tree problems reduce to deciding what information should flow from a child to its parent.
inorder(node):
if node == null:
return
inorder(node.left)
process(node)
inorder(node.right)
preorder(node):
if node == null:
return
process(node)
preorder(node.left)
preorder(node.right)
postorder(node):
if node == null:
return
postorder(node.left)
postorder(node.right)
process(node)
4
/ \
2 6
Inorder: 2 4 6. Preorder: 4 2 6. Postorder: 2 6 4.
Filesystem hierarchies, organizational structures, HTML DOM, expression trees, and database indexes.
| Difficulty | Problem |
|---|---|
| Easy | Binary Tree Inorder Traversal |
| Medium | Binary Tree Level Order Traversal |
| Medium | Binary Tree Right Side View |
| Hard | Serialize and Deserialize Binary Tree |
DFS explores one path as deeply as possible before backtracking. It's natural for trees, graphs, connected components, maze problems, and recursive state exploration. The most important DFS skill isn't memorizing recursion — it's identifying what information should be carried through the recursive call.
DFS(node):
if node is invalid or visited:
return
mark node visited
process(node)
for neighbor in node.neighbors:
DFS(neighbor)
A
/ \
B C
/ \
D E
DFS could visit: A → B → D → E → C.
Maze solving, dependency exploration, connected components, file traversal, cycle detection, and path exploration.
| Difficulty | Problem |
|---|---|
| Easy | Maximum Depth of Binary Tree |
| Medium | Number of Islands |
| Medium | Clone Graph |
| Hard | Word Search II |
BFS explores a graph or tree level by level, making it the natural algorithm for shortest paths in unweighted graphs, minimum number of moves, and level-order tree traversal. BFS first processes everything one step away, then everything two steps away, and so on — which gives it its shortest-path guarantee when every edge has equal cost.
queue = [start]
visited = {start}
while queue:
current = queue.popFront()
process(current)
for neighbor in current.neighbors:
if neighbor not in visited:
visited.add(neighbor)
queue.push(neighbor)
A→B, A→C, B→D, C→E. BFS visits: A, then B C, then D E — one level at a time.
Shortest route in unweighted networks, social-network degrees of separation, minimum moves in games, web crawling, and broadcasting.
| Difficulty | Problem |
|---|---|
| Easy | Minimum Depth of Binary Tree |
| Medium | Binary Tree Level Order Traversal |
| Medium | Rotting Oranges |
| Hard | Word Ladder |
Graphs model relationships rather than simple sequences or hierarchies. Roads connect cities, users connect to friends, services connect through dependencies, computers connect through networks. The most important graph skill is understanding representation — usually an adjacency list — then choosing the correct traversal or algorithm.
build adjacency list
visited = {}
DFS/BFS(start):
mark start visited
for neighbor in graph[start]:
if neighbor not visited:
visit neighbor
A -- B
| |
C -- D
Adjacency list: A: B,C, B: A,D, C: A,D, D: B,C.
Social networks, computer networks, maps, recommendation systems, dependency graphs, and microservice architecture.
| Difficulty | Problem |
|---|---|
| Easy | Find Center of Star Graph |
| Medium | Clone Graph |
| Medium | Number of Provinces |
| Hard | Critical Connections in a Network |
Greedy algorithms make the best-looking choice right now, hoping a sequence of locally optimal decisions produces a globally optimal answer. The important skill is recognizing when this is actually valid — usually via an exchange argument or an invariant showing the current best choice can't hurt the final answer. Greedy problems frequently involve intervals, jumps, scheduling, resource allocation, and optimization.
sort or organize candidates
answer = initial state
for candidate in candidates:
if choosing candidate is provably safe:
choose candidate
update answer
return answer
Jump Game, nums = [2,3,1,1,4]: track the furthest reachable index, maxReach = max(maxReach, i + nums[i]). If maxReach reaches the final index, the destination is reachable.
Scheduling, resource allocation, compression, routing, task selection, and bandwidth allocation.
| Difficulty | Problem |
|---|---|
| Easy | Assign Cookies |
| Medium | Jump Game |
| Medium | Gas Station |
| Hard | Candy |
A Trie is a tree designed specifically for strings. Instead of comparing entire words repeatedly, it stores characters along paths, making prefix operations extremely efficient. Useful whenever a problem asks about prefixes, autocomplete, dictionaries, or searching many words simultaneously.
insert(word):
node = root
for char in word:
if char not in node.children:
node.children[char] = new Node()
node = node.children[char]
node.isWord = true
search(word):
node = root
for char in word:
if char not in node.children:
return false
node = node.children[char]
return node.isWord
Insert cat, car, can → they share ca, then branch into t, r, n.
Autocomplete, spell checkers, search suggestions, dictionaries, prefix filtering, and IP routing concepts.
| Difficulty | Problem |
|---|---|
| Easy | Longest Common Prefix |
| Medium | Implement Trie (Prefix Tree) |
| Medium | Design Add and Search Words Data Structure |
| Hard | Word Search II |
Topological Sort solves ordering problems where dependencies exist: if task B depends on task A, A must appear before B. The underlying structure must be a Directed Acyclic Graph. Two major approaches: DFS postorder, and Kahn's Algorithm using indegrees and a queue. Appears constantly in course scheduling, build systems, package managers, workflow engines, and deployment pipelines.
calculate indegree of every node
queue = all nodes with indegree 0
order = []
while queue:
node = queue.pop()
order.append(node)
for neighbor in graph[node]:
indegree[neighbor]--
if indegree[neighbor] == 0:
queue.push(neighbor)
if len(order) < n:
return "cycle exists"
return order
A→B, A→C, B→D, C→D. Valid order: A → B → C → D (or A → C → B → D).
Package installation, build systems, workflow engines, CI/CD pipelines, course prerequisites, and job dependencies.
Note on difficulty: LeetCode's cleanest Topological Sort problems start at Medium — Find Eventual Safe States is sometimes mistaken for Easy but is listed as Medium. This set is Medium-heavy for the same reason as Dijkstra above. The classic Hard pick, Alien Dictionary, is Premium-only, so this set uses a free Hard problem instead.
| Difficulty | Problem |
|---|---|
| Medium | Course Schedule |
| Medium | Course Schedule II |
| Hard | Parallel Courses III |
Dijkstra solves the shortest-path problem when edges have non-negative weights. The fundamental idea is greedy: repeatedly finalize the currently closest unprocessed node. A priority queue makes this efficient. Dijkstra connects theoretical DSA with real systems such as GPS navigation, network routing, and infrastructure optimization. Remember: standard Dijkstra requires non-negative edge weights.
distance[start] = 0
all other distances = infinity
heap = [(0, start)]
while heap:
dist, node = popMin(heap)
if dist != distance[node]:
continue
for neighbor, weight in graph[node]:
newDist = dist + weight
if newDist < distance[neighbor]:
distance[neighbor] = newDist
push(heap, (newDist, neighbor))
return distance
A --2--> B, A --5--> C, B --1--> C. From A: B=2, C=5. From B: C=3. Shortest A → B → C = 3.
GPS navigation, network routing, delivery optimization, telecom routing, and game pathfinding.
Note on difficulty: LeetCode does not currently have a clean, freely-accessible Easy problem that requires Dijkstra specifically — the closest fits are Medium. Rather than mislabel one, this set is deliberately Medium-heavy.
| Difficulty | Problem |
|---|---|
| Medium | Network Delay Time |
| Medium | Path With Minimum Effort |
| Medium | Cheapest Flights Within K Stops |
| Hard | Swim in Rising Water |
Dynamic Programming is used when a problem has overlapping subproblems and optimal substructure. Instead of solving the same smaller problem repeatedly, store its answer and reuse it. In 1D DP, the state often depends on one or a few previous states. The biggest conceptual shift: stop thinking recursively about every possibility, and instead ask, "What is the smallest piece of information I need to remember to solve the future?"
dp[0] = base case
for i in range(1, n):
dp[i] = best(dp[previous states])
return dp[n - 1]
House Robber, money = [2,7,9,3,1]. At each house: skip it, or rob it plus the best result from two houses ago.
dp[i] = max(dp[i-1], dp[i-2] + money[i])
Financial optimization, resource allocation, scheduling, sequence optimization, and decision systems.
| Difficulty | Problem |
|---|---|
| Easy | Climbing Stairs |
| Medium | House Robber |
| Medium | Longest Increasing Subsequence |
| Hard | Best Time to Buy and Sell Stock IV |
Two-dimensional DP is the natural extension when the answer depends on two dimensions — row and column, two strings, two sequences, or two independent states. Grid problems often have a very clean recurrence because each cell depends on neighboring cells. The critical skill is defining exactly what dp[i][j] means before writing any recurrence.
dp[0][0] = starting value
for i in rows:
for j in columns:
dp[i][j] = best(
dp[i - 1][j],
dp[i][j - 1]
) + current
return dp[m - 1][n - 1]
1 3
2 4
Minimum path sum 1 → 2 → 4 = 7, via dp[i][j] = grid[i][j] + min(dp[i-1][j], dp[i][j-1]).
Route optimization, image processing, text comparison, DNA sequence comparison, and robot navigation.
| Difficulty | Problem |
|---|---|
| Easy | Unique Paths |
| Medium | Minimum Path Sum |
| Medium | Longest Common Subsequence |
| Hard | Edit Distance |
Backtracking is systematic brute force. Instead of blindly generating every possibility, construct a solution incrementally. When a partial solution can no longer lead to a valid answer, immediately undo the decision and try another. Especially powerful for permutations, combinations, subsets, puzzles, constraint satisfaction, and board problems. The three operations to master: choose, explore, unchoose.
backtrack(state):
if solution is complete:
save solution
return
for choice in choices:
make choice
backtrack(new state)
undo choice
Generate subsets of [1,2]: choose 1 → choose 2 → [1,2] → undo 2 → undo 1 → explore [2]. Results: [], [1], [2], [1,2].
Puzzle solving, scheduling possibilities, configuration generation, password/key-space exploration, constraint satisfaction, and game search.
| Difficulty | Problem |
|---|---|
| Easy | Letter Case Permutation |
| Medium | Subsets |
| Medium | Combination Sum |
| Hard | N-Queens |
| # | Pattern | Main Idea |
|---|---|---|
| 1 | Two Pointers | Move two indexes intelligently |
| 2 | Fast & Slow Pointers | Detect cycles / relative positions |
| 3 | Sliding Window | Maintain a valid contiguous range |
| 4 | Prefix Sum | Precompute cumulative information |
| 5 | HashMap | O(1)-average lookup and frequency tracking |
| 6 | Stack | LIFO / nested / monotonic processing |
| 7 | Queue | FIFO / level-by-level processing |
| 8 | Sorting | Create order that enables simpler logic |
| 9 | Binary Search | Eliminate half the search space |
| 10 | Merge Intervals | Sort and combine overlapping ranges |
| 11 | Bitwise | Manipulate binary state efficiently |
| 12 | Heap | Repeatedly access min/max |
| 13 | Top K | Keep only the K candidates that matter |
| 14 | K-Way Merge | Merge multiple sorted sources |
| 15 | Trees | Hierarchical traversal |
| 16 | DFS | Explore deeply |
| 17 | BFS | Explore level by level |
| 18 | Graphs | Model relationships with nodes and edges |
| 19 | Greedy | Make provably safe local choices |
| 20 | Trie | Efficient prefix/string operations |
| 21 | Topological Sort | Order dependency graphs |
| 22 | Dijkstra | Shortest path with non-negative weights |
| 23 | 1D DP | Reuse previous optimal states |
| 24 | 2D DP | State depends on two dimensions |
| 25 | Backtracking | Choose → explore → undo |
The objective is not: "I solved these 100 LeetCode problems."
The objective is: "I can recognize which pattern this problem belongs to."
Master these 25 patterns deeply, and DSA stops being a collection of thousands of unrelated questions. It becomes a relatively small set of ideas that keep appearing in different disguises.
Do not move to the next pattern after merely watching its solution. For every pattern:
Understand the brute-force solution.
Understand why it is too slow.
Identify the pattern.
Implement the pattern from memory.
Solve the Easy problem without help.
Solve both Medium problems.
Attempt the Hard problem.
Revisit the pattern after one week.
Solve one unseen problem using the same pattern.
That is how you turn pattern recognition into interview skill.
Reading about patterns is only the first step. The real progress comes from solving problems, revisiting patterns, and tracking what you can solve independently.
I created a Notion template to help you practice all 25 patterns in one place.
✅ All 25 DSA patterns organized in one dashboard
📊 Pattern-by-pattern mastery tracking
🧠 Main idea for each pattern
🔄 Track your progress from Not Started → Learning → Practicing → Mastered
📝 A dedicated space to practice and review problems
🎯 Built around the same pattern-recognition approach used in this article
Don't just solve more problems. Learn to recognize the pattern behind them.