DSA Gets Easier When You Learn These 8 Patterns

The eight coding interview patterns that turn random DSA practice into a repeatable problem-solving system.

Image Thumbnail: DSA Gets Easier When You Learn These 8 Patterns

You should learn LeetCode patterns because most interview problems are not asking you to invent something completely new.

That was the mistake I made early on.

I treated every problem like a fresh puzzle. New question, new panic, new solution. Some days I solved three problems. Other days I stared at one medium problem for two hours and felt like I had forgotten everything.

Later, the pattern became obvious.

A lot of coding interview problems are just familiar ideas wearing different clothes.

A string problem might secretly be sliding window. A tree problem might just be BFS with level tracking. A “minimum possible answer” problem might be binary search on a condition.

Once you start seeing these patterns, LeetCode becomes less about memorizing solutions and more about recognizing structure.

Image: Pattern Map

The Real Problem: We Learn Problems, Not Patterns

Most beginners prepare like this:

  1. Pick a random LeetCode problem.
  2. Watch a solution after getting stuck.
  3. Understand that specific solution.
  4. Move to the next problem.
  5. Repeat the confusion again.

The issue is not effort. The issue is organization.

If you solve 100 problems without noticing the pattern behind them, every new problem still feels new.

But if you solve 20 problems and deeply understand the pattern, the next 80 become much easier to approach.

Pattern 1: Two Pointers

Two pointers is the base pattern for many linear problems involving arrays, strings, and linked lists.

At first, I assumed two pointers meant only “one pointer at the start and one at the end.”

That is only half of it.

There are two common styles:

Common Style of 2 pointers
function twoSumSorted(nums, target) {
let left = 0;
let right = nums.length - 1;
while (left < right) {
const sum = nums[left] + nums[right];
if (sum === target) return [left, right];
if (sum < target) left++;
else right--;
}
return [-1, -1];
}

This works because the array is sorted. If the sum is too small, moving left increases it. If the sum is too large, moving right decreases it.

The common mistake is using this on unsorted data without thinking about why pointer movement is valid.

Pattern 2: Sliding Window

Sliding window is basically two pointers with a purpose.

Instead of just moving pointers, you maintain a valid range.

This is useful when the problem talks about:

  • subarray
  • substring
  • longest
  • shortest
  • contiguous
  • window
  • at most / at least condition

Example: longest substring without repeating characters.

function lengthOfLongestSubstring(s) {
let left = 0;
let seen = new Set();
let best = 0;
for (let right = 0; right < s.length; right++) {
while (seen.has(s[right])) {
seen.delete(s[left]);
left++;
}
seen.add(s[right]);
best = Math.max(best, right - left + 1);
}
return best;
}

The important part is not the Set.

The important part is this question:

“When does my window become invalid, and how do I make it valid again?”

That question solves many sliding window problems.

Pattern 3: Binary Search Is Not Just for Sorted Arrays

This one surprised me.

For a long time, I thought binary search only worked when the array was sorted.

Then I learned the real condition: binary search works when you can divide the search space using a monotonic condition.

In simpler words:

False, false, false, true, true, true.

If you can convert the problem into this shape, binary search may work.

function firstTrue(n, isValid) {
let left = 0;
let right = n - 1;
let ans = -1;
while (left <= right) {
const mid = Math.floor((left + right) / 2);
if (isValid(mid)) {
ans = mid;
right = mid - 1;
} else {
left = mid + 1;
}
}
return ans;
}

This template becomes useful in problems like:

  • minimum capacity to ship packages
  • split array largest sum
  • first bad version
  • minimum value in rotated sorted array

The hard part is not writing binary search.

The hard part is defining isValid.

Pattern 4: BFS for Level-by-Level Thinking

BFS is usually the right choice when the problem asks for the shortest path, nearest result, or level order traversal.

In trees and graphs, BFS uses a queue.

function levelOrder(root) {
if (!root) return [];
const result = [];
const queue = [root];
while (queue.length > 0) {
const size = queue.length;
const level = [];
for (let i = 0; i < size; i++) {
const node = queue.shift();
level.push(node.val);
if (node.left) queue.push(node.left);
if (node.right) queue.push(node.right);
}
result.push(level);
}
return result;
}

The small detail that matters: capture size before processing a level.

Without that, you mix current-level nodes with next-level nodes.

Pattern 5: DFS for Going Deep

DFS is better when you need to explore complete paths or connected components.

A classic example is number of islands.

function numIslands(grid) {
let count = 0;
const rows = grid.length;
const cols = grid[0].length;
function dfs(r, c) {
if (
r < 0 || c < 0 ||
r >= rows || c >= cols ||
grid[r][c] === "0"
) return;
grid[r][c] = "0";
dfs(r + 1, c);
dfs(r - 1, c);
dfs(r, c + 1);
dfs(r, c - 1);
}
for (let r = 0; r < rows; r++) {
for (let c = 0; c < cols; c++) {
if (grid[r][c] === "1") {
count++;
dfs(r, c);
}
}
}
return count;
}

Here, every DFS call removes one complete island from the grid.

A mistake beginners make is counting every land cell. The correct idea is counting every new DFS start.

Pattern 6: Backtracking Is DFS Where You Build the Tree Yourself

Backtracking feels harder because the tree is not given.

You create it through choices.

Phone number letter combinations is a clean example.

function letterCombinations(digits) {
if (!digits.length) return [];
const map = {
2: "abc", 3: "def", 4: "ghi", 5: "jkl",
6: "mno", 7: "pqrs", 8: "tuv", 9: "wxyz"
};
const result = [];
function backtrack(index, path) {
if (index === digits.length) {
result.push(path.join(""));
return;
}
for (const char of map[digits[index]]) {
path.push(char);
backtrack(index + 1, path);
path.pop();
}
}
backtrack(0, []);
return result;
}

The path.pop() is the real backtracking step.

Without it, choices from one branch leak into another branch.

Pattern 7: Heap for Top K Problems

Whenever a problem says:

  • kth largest
  • kth smallest
  • top k frequent
  • closest k points
  • running median

Think heap.

The counterintuitive part is this:

GoalHeap Usually UsedK largest elementsMin heapK smallest elementsMax heap

Why?

Because when finding k largest values, you only need to remove the smallest among your current selected k values.

Pattern 8: Dynamic Programming

DP is not one pattern. It is a family of patterns.

But the core idea is simple:

Don’t solve the same subproblem again.

Top-down DP feels like recursion with memory.

function fib(n, memo = {}) {
if (n <= 1) return n;
if (memo[n] !== undefined) return memo[n];
memo[n] = fib(n - 1, memo) + fib(n - 2, memo);
return memo[n];
}

Bottom-up DP builds from smaller answers.

function fibBottomUp(n) {
if (n <= 1) return n;
const dp = new Array(n + 1).fill(0);
dp[1] = 1;
for (let i = 2; i <= n; i++) {
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[n];
}

The real DP question is:

“What state represents my problem?”

Once you define the state, transitions become easier.

Reflection: What Changed for Me

After understanding patterns, I stopped asking, “Have I solved this exact problem before?”

I started asking better questions:

  • Is the input linear or nonlinear?
  • Am I tracking a window?
  • Is there a monotonic condition?
  • Do I need shortest path or all paths?
  • Am I solving repeated subproblems?

That shift matters.

In real interviews, you rarely get a problem you have memorized exactly. But you often get a problem that belongs to a pattern you have already practiced.

That is the surprising payoff.

Pattern recognition does not remove problem-solving. It gives your thinking a starting point.

Final Takeaways

If LeetCode feels random, do not just solve more problems.

Group them.

Start with these eight patterns:

  1. Two pointers
  2. Sliding window
  3. Binary search
  4. BFS
  5. DFS
  6. Backtracking
  7. Heap
  8. Dynamic programming

For each pattern, learn the template, solve 5–10 variations, and write down the trigger words that helped you identify it.

That small habit builds interview intuition faster than blindly solving problem after problem.

The next time you open a LeetCode problem, pause before coding.

Ask yourself:

“What pattern is hiding inside this question?”

From Dev Simplified

  • 👏 Enjoyed the article? Don’t forget to leave a clap.
  • 💬 Have thoughts or questions? Share them in the comments.
  • ✍️ Want to write for Dev Simplified? Drop a personal note on any Dev Simplified story with your draft link.