Coding interview prep
April 20, 2026
Sliding Window and Two Pointers: The Two Patterns That Unlock Array Problems
Sliding window and two pointers together cover a huge share of array and string problems in coding interviews. They're often confused — even by people who can solve the problems. This guide shows exactly how each pattern works, when to use each, and the visual intuition that makes them click under pressure.
Most array and string problems in interviews come down to one question: how do you avoid iterating the same elements multiple times? The brute-force approach uses nested loops — O(n²) or worse. Sliding window and two pointers are the techniques that collapse those nested loops into a single linear pass, and knowing when to reach for each one is what separates candidates who solve these problems in 10 minutes from those who get stuck.
We'll cover both patterns from first principles: the core idea, the visual execution, the code template, and the classic problems — with step-by-step array visualizations so you can see exactly what the pointers are doing at each step.
Two Pointers — the core idea
Two pointers uses two indices that move through an array, typically starting at opposite ends (left and right) or both at the start (fast and slow). Instead of checking every possible pair with two nested loops — O(n²) — you move the pointers intelligently based on the current comparison, reducing the solution to O(n).
The key insight: if the input is sorted, you know exactly which direction to move each pointer based on whether the current result is too small or too large. That determinism is what makes O(n) possible.
Variant 1: Opposite-direction pointers (left/right)
Start L at index 0 and R at the last index. Move them toward each other based on the comparison. Stop when they meet or cross.
Two Sum II — sorted array [2, 7, 11, 15], target = 9:
Step 1 — L=0, R=3 → sum = 2+15 = 17 > target. Move R left.
Step 2 — L=0, R=2 → sum = 2+11 = 13 > target. Move R left.
Step 3 — L=0, R=1 → sum = 2+7 = 9 = target. ✓ Found.
function twoSumSorted(numbers, target) {
let left = 0;
let right = numbers.length - 1;
while (left < right) {
const sum = numbers[left] + numbers[right];
if (sum === target) return [left + 1, right + 1]; // 1-indexed
if (sum < target) left++; // need a larger value — move L right
else right--; // need a smaller value — move R left
}
return [];
}The decision logic is the essence of the pattern: if the sum is too small, move the left pointer right (to pick a larger value). If the sum is too large, move the right pointer left (to pick a smaller value). The sort order guarantees these moves always get closer to the target.
Variant 2: Same-direction pointers (fast/slow)
Both pointers start at the same position but move at different speeds. The classic application is cycle detection in a linked list (Floyd's algorithm): if there's a cycle, the fast pointer will eventually lap the slow one. If there's no cycle, the fast pointer reaches null.
The same-direction variant also appears in problems that require removing duplicates in place, finding the middle of a linked list, or partitioning arrays.
function hasCycle(head) {
let slow = head;
let fast = head;
while (fast !== null && fast.next !== null) {
slow = slow.next; // moves 1 step
fast = fast.next.next; // moves 2 steps
if (slow === fast) return true; // they met — cycle exists
}
return false; // fast reached the end — no cycle
}Use Two Pointers when you see these signals
- →Sorted array or linked list — opposite-direction L/R pointers
- →"Find a pair (or triplet) that sums to a target"
- →"Container with most water" / maximize the area between two boundaries
- →"Remove duplicates in place" — fast/slow pointer variant
- →"Detect cycle in a linked list" — fast/slow pointer variant
- →"Find the middle of a linked list" — fast/slow pointer variant
Two Pointers problems, step by step
3Sum (LC 15)
Sort + fix one element + two-pointer scan
Find all unique triplets that sum to zero. The trick: sort the array, iterate over each element as the fixed value, and use opposite-direction two pointers on the rest of the array. O(n²) — much better than the O(n³) brute force.
function threeSum(nums) {
nums.sort((a, b) => a - b);
const result = [];
for (let i = 0; i < nums.length - 2; i++) {
if (i > 0 && nums[i] === nums[i - 1]) continue; // skip duplicates
let left = i + 1;
let right = nums.length - 1;
while (left < right) {
const sum = nums[i] + nums[left] + nums[right];
if (sum === 0) {
result.push([nums[i], nums[left], nums[right]]);
while (left < right && nums[left] === nums[left + 1]) left++;
while (left < right && nums[right] === nums[right - 1]) right--;
left++;
right--;
} else if (sum < 0) {
left++;
} else {
right--;
}
}
}
return result;
}Interview tip: The duplicate-skipping logic is where most candidates get tripped up. Explain it explicitly: "I skip elements equal to the previous one to avoid generating duplicate triplets." That verbalization turns a subtle detail into a visible decision.
Container With Most Water (LC 11)
Maximize area — always move the shorter wall
The greedy insight: the area is limited by the shorter of the two walls. Moving the taller wall inward can only decrease the width without any guarantee of increasing the height — so it's always correct to move the shorter one.
function maxArea(height) {
let left = 0;
let right = height.length - 1;
let maxWater = 0;
while (left < right) {
const width = right - left;
const water = Math.min(height[left], height[right]) * width;
maxWater = Math.max(maxWater, water);
// always move the shorter wall — the greedy decision
if (height[left] < height[right]) left++;
else right--;
}
return maxWater;
}Sliding Window — the core idea
A sliding window maintains a contiguous subarray or substring between two pointers. Instead of recomputing the answer for every possible subarray from scratch — O(n²) — you slide the window forward, adding one element on the right and removing one on the left, updating the answer in O(1) per step. Total: O(n).
Think of it as a view through a glass that moves across the array. At each position, you know exactly what entered the view on the right and what left on the left — you never need to recount what's inside.
Variant 1: Fixed-size window
The window always contains exactly k elements. Slide it one position at a time. At each step: add the new right element, remove the old left element, check the answer.
Maximum sum subarray of size 3 — array [2, 1, 5, 1, 3, 2]:
Window [0..2] → sum = 2+1+5 = 8
Slide right → window [1..3] → sum = 1+5+1 = 7 (add nums[3]=1, remove nums[0]=2)
Slide right → window [2..4] → sum = 5+1+3 = 9 ← maximum
Slide right → window [3..5] → sum = 1+3+2 = 6. Max stays 9.
function maxSumSubarrayOfSizeK(arr, k) {
let windowSum = 0;
let maxSum = 0;
// build the first window
for (let i = 0; i < k; i++) windowSum += arr[i];
maxSum = windowSum;
// slide: add right element, remove left element
for (let right = k; right < arr.length; right++) {
windowSum += arr[right]; // new element enters the window
windowSum -= arr[right - k]; // old element leaves the window
maxSum = Math.max(maxSum, windowSum);
}
return maxSum;
}Variant 2: Variable-size window
The window size adjusts dynamically based on a constraint. The right pointer always expands the window by one. The left pointer shrinks it when the constraint is violated. This is the most common variant in interviews and the one that trips people up.
function longestSubstringWithoutRepeating(s) {
const seen = new Map(); // tracks last-seen index of each char
let left = 0;
let maxLen = 0;
for (let right = 0; right < s.length; right++) {
const char = s[right];
// if char was seen inside the current window, shrink from the left
if (seen.has(char) && seen.get(char) >= left) {
left = seen.get(char) + 1; // jump left past the duplicate
}
seen.set(char, right); // record the latest position of char
maxLen = Math.max(maxLen, right - left + 1);
}
return maxLen;
}The variable window invariant
At every step, the window [left, right] satisfies the constraint. The right pointer always expands. The left pointer only moves forward — never backward. This guarantees O(n): each element enters and leaves the window at most once.
Use Sliding Window when you see these signals
- →"Contiguous subarray or substring" — the window is always contiguous
- →"Longest / shortest subarray that satisfies a condition"
- →"Maximum / minimum sum of a subarray of size k" — fixed window
- →"Find all anagrams in a string" — fixed window with frequency map
- →"Minimum window containing all characters" — variable window with constraint
- →The problem has nested loops in the brute force that both touch adjacent elements
Sliding Window problems, step by step
Permutation in String (LC 567)
Fixed window + frequency map comparison
Check if string s2 contains any permutation of s1. A permutation has the same character frequencies — so you slide a fixed window of size s1.length over s2 and compare frequency maps at each position.
function checkInclusion(s1, s2) {
if (s1.length > s2.length) return false;
const freq1 = new Array(26).fill(0);
const freq2 = new Array(26).fill(0);
const a = 'a'.charCodeAt(0);
for (let i = 0; i < s1.length; i++) {
freq1[s1.charCodeAt(i) - a]++;
freq2[s2.charCodeAt(i) - a]++;
}
let matches = freq1.filter((v, i) => v === freq2[i]).length;
if (matches === 26) return true;
for (let right = s1.length; right < s2.length; right++) {
const left = right - s1.length;
// add right character
const rIdx = s2.charCodeAt(right) - a;
if (freq2[rIdx] === freq1[rIdx]) matches--;
freq2[rIdx]++;
if (freq2[rIdx] === freq1[rIdx]) matches++;
// remove left character
const lIdx = s2.charCodeAt(left) - a;
if (freq2[lIdx] === freq1[lIdx]) matches--;
freq2[lIdx]--;
if (freq2[lIdx] === freq1[lIdx]) matches++;
if (matches === 26) return true;
}
return false;
}Minimum Window Substring (LC 76)
Variable window — expand until valid, then shrink
Find the smallest window in string s containing all characters of string t. This is the hardest sliding window problem — the key is to separate the two phases clearly: expand right until all characters are covered, then shrink left as much as possible while maintaining coverage.
function minWindow(s, t) {
const need = new Map();
for (const c of t) need.set(c, (need.get(c) ?? 0) + 1);
let have = 0;
const required = need.size; // number of unique chars we need
let left = 0;
let minLen = Infinity;
let result = '';
for (let right = 0; right < s.length; right++) {
// EXPAND: add right character to window
const c = s[right];
if (need.has(c)) {
need.set(c, need.get(c) - 1);
if (need.get(c) === 0) have++; // this char is fully satisfied
}
// SHRINK: once all chars are covered, move left forward
while (have === required) {
if (right - left + 1 < minLen) {
minLen = right - left + 1;
result = s.slice(left, right + 1);
}
const leftChar = s[left];
if (need.has(leftChar)) {
need.set(leftChar, need.get(leftChar) + 1);
if (need.get(leftChar) > 0) have--; // coverage lost
}
left++;
}
}
return result;
}Interview tip: State the two phases out loud before coding: "First I expand right until the window is valid. Then I shrink left as much as possible, recording the minimum. Then I expand again." Interviewers at top companies want to see that you understand the two-phase structure — not just that you can write the code.
Two Pointers vs Sliding Window — what's the actual difference?
Here's the thing most guides don't explain clearly: sliding window is a specialized form of two pointers. Both use a left and a right index. The difference is in what they optimize for and how the pointers move.
Two Pointers vs Sliding Window
Reach for Two Pointers when…
- → The array is sorted and you need pairs
- → The problem asks about a relationship between two specific elements
- → You're working with a linked list (fast/slow)
- → The elements you compare don't need to be adjacent
Reach for Sliding Window when…
- → The problem says "subarray" or "substring"
- → You need max/min/longest/shortest of a contiguous range
- → A constraint must be maintained across a range of elements
- → The brute force has nested loops over adjacent elements
The one question that decides which to use
Does the problem ask about a contiguous range of elements? If yes → sliding window. If the elements you compare can be anywhere in the array (not necessarily adjacent), and the array is sorted → two pointers. If neither condition clearly applies, check whether you need a frequency map (strong sliding window signal) or whether sorting the input would make the problem easier (strong two pointers signal).
How to recognize these patterns in an interview
The real skill isn't knowing how to implement two pointers or sliding window — it's recognizing which one applies within the first 2 minutes of reading a problem. Here's the vocabulary that signals each pattern:
Two Pointers signals
"Sorted array""Find a pair / triplet that sums to target""Remove duplicates in place""Reverse an array or string""Detect cycle" / "find midpoint" (linked list)"Is the string a palindrome?"
Sliding Window signals
"Contiguous subarray / substring""Longest / shortest subarray with property""Maximum sum of k elements""Minimum window containing all chars""All anagrams in a string""At most k distinct characters"
Practice reading the problem statement and writing the pattern name before looking at any solution. That 30-second recognition reflex is the skill that transfers directly to interviews — where you have 5 minutes before you're expected to start coding.
How to study these patterns so they stick
- Learn them visually first. Before writing any code, trace the pointer movements on paper for a small example. Where does L start? Where does R start? When does each one move? Can you predict the next state from the current one? If yes, you understand the pattern. If not, you've memorized the code without the intuition.
- Code the template, not the problem. Learn the general sliding window template (fixed and variable) and the two-pointer template before solving any specific problem. When the template is solid, each new problem just fills in the details.
- Solve problems in clusters. Do 4–5 two-pointer problems in a row, then 4–5 sliding window problems. Interleaving different patterns feels productive but produces shallow recognition. Clustering builds the deep pattern-matching that transfers to unfamiliar problems.
- Write the invariant in words. For every problem you solve, write one sentence: "The window always satisfies X" or "The pointers always maintain the property that Y." Saying this out loud in an interview shows structural thinking — not just a working solution.
- Time yourself. Both patterns should click within 5 minutes of reading the problem. If they don't, the bottleneck is recognition, not implementation — solve more problems in the same cluster without looking at solutions first.
Frequently asked questions
What is the sliding window technique in coding interviews?
The sliding window technique uses two pointers to maintain a contiguous subarray or substring between them. Instead of recomputing the answer from scratch for every subarray (O(n²)), you slide the window forward — adding one element on the right and removing one on the left — updating the answer in O(1) per step. This brings the total complexity to O(n). Classic problems: Longest Substring Without Repeating Characters (LC 3), Minimum Window Substring (LC 76), Permutation in String (LC 567).
What is the two pointers technique?
Two pointers uses two indices that move through an array to avoid checking every possible pair with nested loops. The opposite-direction variant (L starts at 0, R at the end) works on sorted arrays to find pairs that satisfy a condition. The same-direction variant (fast/slow) works on linked lists for cycle detection and midpoint finding. Classic problems: Two Sum II (LC 167), 3Sum (LC 15), Container With Most Water (LC 11), Linked List Cycle (LC 141).
What is the difference between sliding window and two pointers?
Sliding window is a specialized form of two pointers for contiguous subarrays or substrings. Both use a left and right pointer. The key difference: sliding window always maintains a contiguous range and both pointers only move right; standard two pointers (opposite direction) work on sorted arrays where the elements compared don't need to be adjacent. If the problem says 'subarray' or 'substring', reach for sliding window. If the array is sorted and you need pairs or triplets, reach for two pointers.
When should I use a fixed vs variable sliding window?
Use a fixed window when the problem specifies the exact size k (e.g., 'maximum sum subarray of size k', 'find all anagrams of a length-n string'). Use a variable window when the size adjusts based on a constraint (e.g., 'longest substring without repeating characters', 'minimum window containing all target characters'). In variable windows, the right pointer always expands; the left pointer only moves forward when the constraint is violated.
What are the most important sliding window problems for coding interviews?
The six problems that cover all sliding window variants: Maximum Sum Subarray of Size K (fixed window foundation), Longest Substring Without Repeating Characters (LC 3, variable window with hash set), Permutation in String (LC 567, fixed window with frequency map), Minimum Window Substring (LC 76, variable window with two-phase expand/shrink), Longest Repeating Character Replacement (LC 424), and Fruit Into Baskets (LC 904, at-most-k constraint). Mastering these six covers every sliding window pattern you'll encounter.
Summary
Two pointers and sliding window are the two patterns that eliminate nested loops for array and string problems. Two pointers works by moving two indices toward each other (or at different speeds) through a sorted array or linked list. Sliding window works by maintaining a contiguous subarray whose size expands and contracts based on a constraint.
The decision framework is straightforward: if the problem involves a contiguous subarray or substring, reach for sliding window. If the array is sorted and you need relationships between pairs of elements, reach for two pointers. If neither is obvious, check whether sorting the input would create a useful property — a strong signal for two pointers.
Both patterns are learnable in a week with focused practice: learn the templates, cluster your problem sets, and train recognition before implementation. At that point, these patterns stop feeling like tricks and start feeling like natural tools.
Explore more
- The 7 algorithm patterns that cover 80% of interview problems →Where sliding window and two pointers fit in the full landscape
- Graph algorithms: when to use BFS, DFS, or Dijkstra →The next pattern cluster after arrays and strings
- Dynamic programming explained visually →Memoization, tabulation, and the 5 DP patterns — step by step
- Interactive algorithm visualizer →See sliding window, two pointers, BFS, and DP run step by step with your own code
See sliding window and two pointers execute — step by step
Expora's visual debugger shows you the pointers moving in real time — with your own code, in any language. Watch the window expand and shrink. Watch L and R converge. See exactly what changes at each step and why. This is the visualization that turns pattern knowledge into pattern intuition.