There Are Only 4 Types of Two Pointer Problems — Here’s Every One of Them
I failed interviews thinking every sliding window question was a new puzzle. It wasn’t. It was always one of these four.

There Are Only 4 Types of Two Pointer Problems — Here’s Every One of Them
Let me be honest with you.
The first time I got a “find the longest subarray with sum ≤ K” question in an interview, I panicked and wrote a brute force with two nested loops. It worked. It was also O(n²), and the interviewer’s face told me everything I needed to know.
The second time I got a similar question — different company, different array, same core idea — I panicked again. Different variables, same panic.
Sound familiar? If you’ve ever felt like every “sliding window” question is a brand-new puzzle instead of the same puzzle wearing a different look, this one’s for you.
Because here’s the thing nobody tells you early enough: there are only four types of sliding window problems.
Once you can recognize which of the four you’re staring at, the code basically writes itself. I’m going to walk through all four, with real code, real examples, and the one-line optimization that quietly turns an O(2n) solution into an O(n) one — the kind of detail that makes an interviewer sit up a little straighter.
Wait, what even is a “window”?
A window is just a consecutive chunk of an array or string, marked by two pointers — left and right.
[ -1, 2, 3, 3, 2, 8, -1, 7 ]
L Rright expands the window (adds new elements in). left shrinks it (kicks old elements out). That's it. That's the whole toy.
Everything else is just deciding when to expand and when to shrink — and that decision depends entirely on which of the four patterns you're dealing with.
Have you noticed how every “two pointer” tutorial shows you the same three-line loop and then throws you into a LeetCode problem with zero explanation of why the pointers move the way they do?
Yeah. Let’s actually fix that.
Pattern 1: The Fixed-Size Window
This is the easiest one, and honestly, it barely shows up in real interviews anymore — but it’s the perfect place to build intuition.
The question: Given an array, find the maximum sum of any 4 consecutive elements.
You already know the window size (K = 4) before you even start. So the logic is dead simple:
function maxSumFixedWindow(arr, k) {
let windowSum = 0;
// Build the very first window
for (let i = 0; i < k; i++) {
windowSum += arr[i];
}
let maxSum = windowSum;
// Slide the window one step at a time
for (let right = k; right < arr.length; right++) {
windowSum += arr[right]; // add the new element coming in
windowSum -= arr[right - k]; // remove the oldest element leaving
maxSum = Math.max(maxSum, windowSum);
}
return maxSum;
}
console.log(maxSumFixedWindow([-1, 2, 3, 3, 2, 8, -1, 7], 4)); // 16Notice what’s happening: instead of recalculating the sum from scratch every time (which would be O(n·k)), you just add one number and remove one number. One in, one out. That’s the entire “sliding” part of sliding window.
Quick gut-check question for you: if K were the size of the entire array, what would this loop even do? (Answer at the bottom — comment your guess first 👇)
Pattern 2: Longest Subarray/Substring With a Condition
This is the one you’ll actually meet in interviews — again, and again, and again. If you only master one pattern from this article, make it this one.
The question: Find the longest subarray where the sum is ≤ K.
Here’s the mental model that finally made this click for me: think of it as a rubber band. You keep stretching it (right++) as long as it doesn't snap. The moment it snaps (condition violated), you let go a little from the left — not all the way — just enough until it's valid again.
function longestSubarray(arr, k) {
let left = 0;
let sum = 0;
let maxLength = 0;
for (let right = 0; right < arr.length; right++) {
sum += arr[right]; // expand: always grow the window first
// shrink ONLY when we've broken the rule
while (sum > k) {
sum -= arr[left];
left++;
}
// window is valid now, try to claim the record
maxLength = Math.max(maxLength, right - left + 1);
}
return maxLength;
}
console.log(longestSubarray([2, 5, 1, 7, 10], 14)); // 3 -> [2,5,1]Why does this run in O(n) and not O(n²)?
Because right only ever moves forward across the whole array — that's n steps, max. And left?
It also only ever moves forward, never backward. So even though it looks like a nested loop, both pointers together only take n + n = 2n steps total, not n × n. Big difference when n is a million.
Real talk: this is the exact optimization interviewers are quietly grading you on. Anyone can write the brute force. Explaining why left never resets is what separates “can code” from “understands what they’re coding.”
A trap to watch for: if the question asks you to return the actual subarray (not just its length), you can’t get lazy with shrinking — you have to shrink properly and track the real left/right positions. If it only asks for the length, there’s a sneaky trick: don’t shrink back below your current best length, since a smaller window won’t beat your record anyway. Small detail, but it’s the difference between a working solution and an optimal one. Has this “wait, do I even need to shrink fully” moment ever tripped you up? I’d genuinely love to know in the comments.
Pattern 3: Counting Subarrays That Match Exactly
This one messes with people’s heads the first time, myself included.
The question: Count the number of subarrays with sum exactly equal to K.
Your gut says “just do pattern 2 but check for equality instead of ≤.” Try it. You’ll get stuck fast — because with an exact-match condition, you genuinely cannot tell whether to expand or shrink. The sum could be too low or too high, and equality gives you zero signal about which direction to move. It’s not that you’re bad at this — the pattern itself is ambiguous by design.
The trick that unlocks it: stop trying to count “exactly K” directly. Count “at most K” instead — which behaves beautifully with expand/shrink logic — and do it twice:
count(sum == K) = count(sum <= K) - count(sum <= K-1)function atMostK(arr, k) {
if (k < 0) return 0;
let left = 0, sum = 0, count = 0;
for (let right = 0; right < arr.length; right++) {
sum += arr[right];
while (sum > k) {
sum -= arr[left];
left++;
}
// every subarray ending at `right`, starting anywhere from
// left..right, has a sum <= k
count += right - left + 1;
}
return count;
}
function countSubarraysEqualK(arr, k) {
return atMostK(arr, k) - atMostK(arr, k - 1);
}That last line inside the loop — count += right - left + 1 — is the part people skim past, but it's the clever bit: every subarray ending at right and starting anywhere between left and right automatically satisfies "sum ≤ k," so you get to count a whole batch of valid subarrays in one shot, instead of checking them one by one.
Isn’t it kind of satisfying that you can solve an “exactly equal” problem without ever writing an equality check? That subtraction trick shows up in a ton of counting problems once you know to look for it.
Pattern 4: Shortest / Minimum Window
The mirror image of Pattern 2. Instead of stretching a rubber band and shrinking only when it snaps, you flip the goal: find any valid window first, then aggressively shrink it to see how small it can get before it breaks.
function shortestSubarrayAtLeastK(arr, k) {
let left = 0, sum = 0;
let minLength = Infinity;
for (let right = 0; right < arr.length; right++) {
sum += arr[right]; // expand until valid
// once valid, shrink as much as possible
while (sum >= k) {
minLength = Math.min(minLength, right - left + 1);
sum -= arr[left];
left++;
}
}
return minLength === Infinity ? 0 : minLength;
}Notice the direction flip: in Pattern 2 you shrink because the window broke. Here you shrink because the window is already good enough and you’re greedy for something smaller.
Same two pointers, same skeleton — completely different intent. This is exactly why memorizing code without understanding intent falls apart the moment the problem statement changes even slightly.
The One Template Behind All Four
If you zoom out, every single one of these problems follows the same skeleton:
let left = 0;
// initialize your tracking variable(s) — sum, count, map, whatever the problem needs
for (let right = 0; right < arr.length; right++) {
// 1. EXPAND - always happens first
// add arr[right] into your tracking state
// 2. FIX - shrink from the left while the window is invalid
// (or, for pattern 4, while it's still valid and you're being greedy)
while (/* condition */) {
// remove arr[left] from tracking state
left++;
}
// 3. RECORD - update your answer using the current window
}Expand. Fix. Record. That’s the whole game. Four patterns, one skeleton, and the only thing that changes is what “fix” and “record” mean for your specific problem.
I wish someone had drawn this out for me before my third failed interview instead of after.
So, what’s the actual takeaway here?
Honestly? It’s this: DSA patterns aren’t about memorizing solutions — they’re about recognizing shape. The moment I stopped treating every sliding window question as unique and started asking myself “is this fixed-size, longest-with-condition, count-exact, or shortest-window?” — my solve time dropped by more than half. Not because I got smarter overnight, but because I stopped reinventing the wheel every single time.
If you’re prepping for interviews right now, don’t chase 200 random problems on autopilot. Chase the four shapes. Solve two or three problems per pattern, really understand why left and right move the way they do, and you’ll be able to reason through a sliding window problem you’ve never seen before — which, let’s be honest, is the entire point of these interviews anyway.
Quick answer to the earlier question: if K equals the array length, the fixed window loop’s second for loop simply never runs — there's only one possible window, the whole array itself.
Which of these four have you actually hit in a real interview? And be honest — did you brute-force your way through it like I did the first time? Drop it in the comments, I’m curious how universal this is.
From Tech By Neha Gupta
👏 Enjoyed the article? Don’t forget to leave a clap.
💬 Have thoughts or questions? Share them in the comments.