Why These Two Patterns Dominate Interviews
If you could only learn two coding interview patterns, make them sliding window and two pointers. Together, they cover roughly 25-30% of all array and string problems you'll encounter in technical interviews at companies like Google, Meta, Amazon, and every startup in between. The reason is simple: both patterns transform brute-force O(n^2) solutions into O(n) solutions, and recognizing when to apply them is exactly the kind of pattern-matching skill that interviewers are testing.
The core idea behind both patterns is the same: instead of re-examining elements you've already seen, maintain state as you move through the input. A sliding window maintains a contiguous subarray or substring. Two pointers maintain positions that converge or diverge based on some condition. The difference is subtle but important, and confusing them is one of the most common mistakes candidates make.
Here's the thing that most LeetCode grinders miss: memorizing solutions to individual problems is brittle. You see a slight variation in the interview and your memorized solution doesn't transfer. But if you deeply understand why a sliding window works -- the invariant it maintains, the state it tracks, the condition that triggers expansion or contraction -- you can adapt to any variation. That's the goal of this guide.
Before diving in, it helps to understand where these patterns sit in the broader landscape. If you're building a systematic approach to pattern recognition, our coding interview patterns overview covers all the major categories. For problems where these patterns don't apply and you need to break the problem into subproblems, see our dynamic programming guide.
Sliding Window: Fixed Size vs Variable Size
A sliding window is a subarray (or substring) that moves across the input. The window has a left boundary and a right boundary, and as right advances, you add the new element to your running state. The question is: when do you move left?
This is where the two flavors diverge:
Fixed-Size Window
The window size k is given to you in the problem. You expand right until the window hits size k, compute your result, then slide both boundaries forward by one. This is the simpler variant.
The template:
- Initialize your running state (sum, count, frequency map, etc.)
- Expand
rightfrom 0 ton-1 - Add
arr[right]to your state - If
right - left + 1 == k: record the result, removearr[left]from state, incrementleft
The beauty of the fixed-size window is its predictability. You always know exactly when to shrink -- when the window exceeds size k. There's no ambiguity, no conditional logic around whether to shrink. This makes it the ideal starting point for learning the pattern. If you can write a fixed-size sliding window from memory in under two minutes, you're ready for the variable-size variant.
Variable-Size Window
The window size isn't fixed. Instead, you're asked to find the longest or shortest subarray/substring satisfying some condition. You expand right to grow the window. When the window violates the condition, you shrink from left until it's valid again.
The template:
- Initialize your running state
- Expand
rightfrom 0 ton-1 - Add
arr[right]to your state - While the window is invalid: remove
arr[left]from state, incrementleft - Update your answer (current window is the best valid window ending at
right)
Key insight: In a variable-size sliding window, the shrink step is where candidates lose points. You must know exactly what condition triggers the shrink, and you must shrink just enough to restore validity -- not all the way back to the start. Getting this wrong turns your O(n) solution into O(n^2) or produces incorrect results.
Two Pointers: When and Why
Two pointers is a broader technique. While sliding window always uses a contiguous range, two pointers can work on sorted arrays, linked lists, or any structure where two indices moving in coordinated directions eliminate unnecessary work.
The most common setups:
- Opposite ends: One pointer starts at the beginning, one at the end. They move toward each other. Used for pair-sum problems on sorted arrays, container problems, palindrome checks.
- Same direction: Both pointers start at the beginning. One moves faster than the other. Used for removing duplicates, cycle detection, partitioning.
- Multiple arrays: One pointer per array, advancing based on comparison. Used for merging sorted arrays, intersection problems.
The decision of which pointer to move is always driven by a comparison or condition. If you can't articulate "I move the left pointer when X and the right pointer when Y," you don't have a two-pointer solution -- you're guessing.
When to Use Which
| Characteristic | Sliding Window | Two Pointers |
|---|---|---|
| Input type | Array or string | Array, string, or linked list |
| What you're finding | Contiguous subarray/substring | Pair, triplet, or partition point |
| Sorted input required? | No | Often yes (for opposite-end variant) |
| Window contiguity | Always contiguous | Not necessarily |
| Typical problems | Max/min subarray, substring with condition | Two-sum (sorted), 3sum, container with water |
| State tracking | Running sum, frequency map, count | Usually just the pointer positions |
A useful heuristic: if the problem says "subarray" or "substring," think sliding window first. If it says "pair," "triplet," or the input is sorted, think two pointers first. Some problems use both.
Worked Examples: Sliding Window
Example 1: Maximum Sum Subarray of Size K (Easy)
Problem: Given an array of integers and a number k, find the maximum sum of any contiguous subarray of size k.
What the interviewer expects to hear: "The brute-force approach computes the sum of every subarray of size k, which is O(n*k). But most of the work is redundant -- when we slide the window one position right, we're re-summing k-1 elements we already summed. Instead, I'll maintain a running sum: add the new element entering the window, subtract the element leaving."
Approach:
- Initialize
windowSum = 0andmaxSum = -infinity - For each index
rightfrom 0 ton-1: addarr[right]towindowSum - If
right >= k - 1: updatemaxSum = max(maxSum, windowSum), then subtractarr[right - k + 1]fromwindowSum - Return
maxSum
Complexity: O(n) time, O(1) space.
This is the canonical fixed-window problem and a good warm-up to verify you understand the pattern before tackling harder variants. Notice that each element is added to the sum exactly once and removed exactly once. That's the fundamental insight behind every sliding window problem: each element participates in at most two operations (enter and leave), giving you the O(n) guarantee regardless of window size. A brute-force approach would redundantly sum overlapping elements, doing O(k) work per position instead of O(1).
Example 2: Longest Substring Without Repeating Characters (Medium)
Problem: Given a string s, find the length of the longest substring without repeating characters.
What the interviewer expects to hear: "I need a variable-size window. I'll expand the right boundary one character at a time. I'll track character frequencies in a hash map. When a character repeats -- meaning its frequency exceeds 1 -- I shrink from the left until every character in the window is unique again."
Approach:
- Initialize
left = 0, a hash mapcharIndexmapping each character to its most recent index, andmaxLen = 0 - For each
rightfrom 0 ton-1: - If
s[right]is incharIndexandcharIndex[s[right]] >= left: setleft = charIndex[s[right]] + 1 - Set
charIndex[s[right]] = right - Update
maxLen = max(maxLen, right - left + 1)
Complexity: O(n) time, O(min(n, alphabet_size)) space.
Common mistake: Using a frequency map and shrinking one character at a time with a while loop. This works but is slower in practice. The optimization above -- jumping left directly to charIndex[s[right]] + 1 -- is what separates a good answer from a great one. The interviewer will notice.
Why this problem matters: It appears in interviews at Google, Amazon, Meta, and Bloomberg with remarkable frequency. Variations include "longest substring with at most K distinct characters" and "longest substring with at most K replacements" (Longest Repeating Character Replacement). Once you internalize the variable-size window template, all these variations become straightforward -- you just swap in a different validity condition.
Example 3: Minimum Window Substring (Hard)
Problem: Given strings s and t, find the minimum window in s that contains all characters of t.
What the interviewer expects to hear: "This is a variable-size sliding window where the validity condition is: every character in t appears in the window with at least the required frequency. I'll use two frequency maps -- one for t and one for my current window -- and track how many characters are fully satisfied. When all are satisfied, I try to shrink from the left to find the minimum."
Approach:
- Build a frequency map
needfromt. Trackrequired= number of unique characters intandformed= 0 - Initialize
left = 0, a window frequency mapwindowCounts, and result variables - For each
rightfrom 0 tolen(s)-1: adds[right]towindowCounts. IfwindowCounts[s[right]] == need[s[right]], incrementformed - While
formed == required: update the result if current window is smaller, then removes[left]fromwindowCounts. IfwindowCounts[s[left]] < need[s[left]], decrementformed. Incrementleft - Return the minimum window found
Complexity: O(|s| + |t|) time, O(|s| + |t|) space.
This problem is the ultimate test of the variable-size window pattern. The formed variable is the key insight -- without it, you'd need to compare entire frequency maps on every step, degrading performance. Interviewers at top companies consider this a strong signal problem: if you can implement it cleanly and explain the shrink condition clearly, you're demonstrating genuine pattern mastery.
A note on the shrink loop: The inner while loop might look like it makes this O(n^2), but it doesn't. Each character is added to the window exactly once (when right advances) and removed at most once (when left advances). Over the entire execution, left moves at most n times total. This amortized analysis is critical to understand -- interviewers will ask you to justify the O(n) claim, and "the left pointer never moves backward" is the key argument.
Worked Examples: Two Pointers
Example 4: Container With Most Water (Medium)
Problem: Given an array height where height[i] is the height of a line at position i, find two lines that together with the x-axis form a container holding the most water.
What the interviewer expects to hear: "Brute force checks all pairs in O(n^2). But I can use two pointers starting at opposite ends. The area is min(height[left], height[right]) * (right - left). The width decreases as pointers converge, so the only way to find a larger area is to increase the height. I always move the pointer pointing to the shorter line, because moving the taller one can only decrease or maintain the minimum height."
Approach:
- Initialize
left = 0,right = n-1,maxArea = 0 - While
left < right: computearea = min(height[left], height[right]) * (right - left). UpdatemaxArea - If
height[left] < height[right]: incrementleft. Else: decrementright - Return
maxArea
Complexity: O(n) time, O(1) space.
Why this works: The greedy argument is subtle. By moving the shorter pointer, you might find a taller line that increases the area despite the reduced width. Moving the taller pointer can never help because the area is bounded by the shorter side. This is the kind of reasoning interviewers want to hear -- not just "I move the smaller one" but why that's provably correct.
Example 5: 3Sum (Medium)
Problem: Given an array nums, find all unique triplets [a, b, c] such that a + b + c == 0.
What the interviewer expects to hear: "I'll sort the array first. Then for each element nums[i], I need to find two elements in the remaining array that sum to -nums[i]. This is a two-sum problem on a sorted array, which I can solve with two pointers in O(n). Total: O(n^2), which is optimal for this problem. The tricky part is handling duplicates."
Approach:
- Sort
nums. For each indexifrom 0 ton-3: - Skip if
nums[i] == nums[i-1](avoid duplicate triplets from the first element) - Set
left = i+1,right = n-1,target = -nums[i] - While
left < right: computesum = nums[left] + nums[right] - If
sum == target: record triplet, skip duplicates by advancingleftpast identical values and decrementingrightpast identical values - If
sum < target: incrementleft. Ifsum > target: decrementright
Complexity: O(n^2) time (sort is O(n log n), dominated by the nested loop), O(1) extra space (ignoring output).
Common mistake: Forgetting to skip duplicates at both levels. You need to skip duplicates for the outer i loop and for the inner left/right pointers after finding a match. Missing either one produces duplicate triplets in your output, which is an instant bug that interviewers will catch.
Interview tip: When you explain 3Sum, explicitly mention the reduction technique: "I'm reducing a 3-pointer problem to a fixed outer loop plus a 2-pointer inner problem." This framing shows the interviewer you understand the general principle, not just this specific problem. The same reduction applies to 4Sum (fix two elements, run two pointers on the rest) and kSum in general. Interviewers love seeing candidates who can generalize.
Example 6: Trapping Rain Water (Hard)
Problem: Given n non-negative integers representing an elevation map, compute how much water it can trap after raining.
What the interviewer expects to hear: "Water at each position equals min(maxLeft, maxRight) - height[i]. I could precompute prefix and suffix max arrays in O(n) space. But I can do it in O(1) space with two pointers. I maintain leftMax and rightMax as I converge the pointers. The key insight is: if leftMax < rightMax, the water at left is determined by leftMax regardless of what's between the pointers, because we already know there's something at least as tall as rightMax on the right side."
Approach:
- Initialize
left = 0,right = n-1,leftMax = 0,rightMax = 0,water = 0 - While
left < right: - If
height[left] < height[right]: ifheight[left] >= leftMax, updateleftMax. Else, addleftMax - height[left]towater. Incrementleft - Else: if
height[right] >= rightMax, updaterightMax. Else, addrightMax - height[right]towater. Decrementright - Return
water
Complexity: O(n) time, O(1) space.
This is a classic hard problem that combines two pointers with a greedy insight. Many candidates know the prefix/suffix max approach but can't explain the two-pointer optimization. If you can walk through why leftMax < rightMax guarantees correctness at the left pointer, you're demonstrating the depth that gets Strong Hire ratings at top companies.
The proof, simplified: When height[left] < height[right], we know that rightMax >= height[right] > height[left]. So the water at position left is bounded by leftMax (since leftMax is the smaller of the two maxes). We don't need to know the exact value of rightMax -- we just need to know it's large enough not to be the bottleneck. This is why the algorithm works without precomputing the full suffix max array. Practice articulating this proof clearly; it takes most candidates 2-3 attempts before they can explain it without tripping over the logic.
Common Mistakes and How to Avoid Them
Off-by-One Errors
The most frequent source of bugs. In sliding window problems, the window size is right - left + 1, not right - left. When the problem asks for size k, your condition is right - left + 1 == k or equivalently right >= k - 1 (if left starts at right - k + 1). Write out a small example with 3-4 elements and trace your indices by hand before coding. It takes 30 seconds and prevents 5 minutes of debugging.
Forgetting to Shrink the Window
In variable-size window problems, failing to shrink properly leads to two outcomes: either your window grows forever (wrong answer) or you shrink too aggressively (miss valid solutions). The fix: write your shrink condition explicitly before coding. "I shrink when [specific condition]." If you can't state it in one sentence, you haven't understood the problem yet.
Not Handling Edge Cases
Every sliding window and two pointer solution needs to handle:
- Empty input (return 0, empty array, or empty string)
- Input smaller than the window size (for fixed-window problems)
- All identical elements
- Single-element input
State these edge cases out loud before you start coding. Interviewers give credit for identifying them even if your code handles them naturally. A surprisingly effective technique: before writing any code, say "Let me check the edge cases" and walk through one or two. This shows the interviewer you think defensively, which is exactly what they want from someone who'll be writing production code.
Confusing the Two Patterns
Candidates sometimes force a sliding window onto a problem that needs two pointers, or vice versa. The giveaway: if you find yourself maintaining a contiguous range and tracking aggregate state (sum, frequency, count), it's a sliding window. If you're comparing values at two positions and deciding which pointer to move based on a comparison, it's two pointers. If you're doing both, that's fine too -- some problems genuinely combine them.
Using the Wrong Data Structure Inside the Window
For variable-size windows, the state you maintain inside the window matters. A common trap: using an array or list to store window elements and scanning it linearly on every step. This destroys your O(n) guarantee. Use a hash map for frequency counting, a deque for sliding window maximum/minimum, or a sorted structure when you need ordered access. The choice of inner data structure determines whether your "O(n)" solution is actually O(n) or secretly O(n*k).
Not Verbalizing Your Invariant
The single best habit you can build for interview performance: before writing any code, state your invariant out loud. "At every step, my window contains [specific property]." or "My left pointer always points to [specific condition]." Interviewers consistently rate candidates higher when they hear an explicit invariant statement. It shows you're reasoning about correctness, not just hacking until the test cases pass.
Problem Difficulty Progression
Use this table to structure your practice. Start from the top and work down. If you can solve the hard problems cleanly in an interview setting, you've mastered both patterns.
| Difficulty | Problem | Pattern | Key Concept |
|---|---|---|---|
| Easy | Max Sum Subarray of Size K | Fixed sliding window | Running sum maintenance |
| Easy | Two Sum (sorted array) | Two pointers (opposite ends) | Sum comparison drives pointer movement |
| Medium | Longest Substring Without Repeating Chars | Variable sliding window | Hash map for last-seen index |
| Medium | Container With Most Water | Two pointers (opposite ends) | Greedy: move the shorter side |
| Medium | 3Sum | Sort + two pointers | Reduce to two-sum, handle duplicates |
| Medium | Longest Repeating Character Replacement | Variable sliding window | Window valid when size - maxFreq <= k |
| Hard | Minimum Window Substring | Variable sliding window | Two frequency maps, formed counter |
| Hard | Trapping Rain Water | Two pointers | Left/right max with convergence proof |
| Hard | Substring with Concatenation of All Words | Fixed sliding window + hashing | Window of word-length chunks |
Putting It All Together
The difference between candidates who pass coding interviews and those who don't often comes down to pattern recognition speed. When you see a problem involving contiguous subarrays, your brain should immediately flag "sliding window" and you should be able to classify it as fixed or variable within 30 seconds. When you see a sorted array with a pair or triplet target, "two pointers" should be automatic.
Here's a concrete practice plan:
- Week 1: Solve 3 fixed-window and 3 variable-window problems. Focus on writing the template from memory and getting the shrink condition right on the first try.
- Week 2: Solve 3 opposite-end and 2 same-direction two-pointer problems. Pay attention to the termination condition and how you decide which pointer to move.
- Week 3: Mix them together. Do 5 problems without being told which pattern to use. The identification step is what you're practicing now.
Once you're comfortable with the patterns on paper, practice under interview conditions. Time yourself to 25 minutes per problem. Explain your approach out loud as you code. If you want realistic pressure, system design interviews require the same structured thinking at a higher level of abstraction -- practicing both in parallel builds complementary skills. You can also run timed coding mock interviews on Hoppers to simulate real interview pressure with immediate feedback on your approach and solution quality.
These two patterns are your highest-ROI investment for coding interviews. Learn them deeply, practice until they're automatic, and you'll find that a surprising number of "hard" problems become manageable.