Home/Learn/Sliding Window

Pattern Guide

Sliding Window

"Turn O(n²) subarray loops into O(n) with a moving window."

Master fixed-size windows, dynamic windows, and the exact-count formula. The pattern that shows up in 80% of subarray/substring problems.

Problems you can solve with this pattern

10 problems · click any to start solving

All sliding window
1Longest Substring Without Repeating CharactersMediumSolve
2Maximum Number of Vowels in a Substring of Given LengthMediumSolve
3Binary Subarrays With SumMediumSolve
4Count Number of Nice SubarraysMediumSolve
Fixed window (size k)
// Build first window, then slide
let sum = 0;
for (let i = 0; i < k; i++) sum += nums[i];
let maxSum = sum;

for (let i = k; i < n; i++) {
    sum += nums[i];       // add incoming right element
    sum -= nums[i - k];   // remove outgoing left element
    maxSum = Math.max(maxSum, sum);
}

Every subarray problem at its core asks: "find a contiguous part of the array that satisfies some condition." The brute force is always two nested loops — O(n²). Sliding window is the observation that you don't need to recompute from scratch for each starting point. You can maintain a running state and slide a window across the array, adding from the right and removing from the left.

Two window types exist:
Fixed window — the size k is given upfront, window is always exactly k wide.
Dynamic window — the window grows and shrinks based on a condition (longest valid / shortest valid).

When to Reach for Sliding Window

See This in the ProblemThink This
subarray / substringSliding window candidate
"longest ... with condition"Dynamic window, expand until broken, shrink from left
"shortest ... with condition"Dynamic window, shrink greedily once valid
"fixed length k"Fixed window, slide one step at a time
"count of subarrays with exactly k"atMost(k) − atMost(k−1) formula
input is ALL positiveTwo pointer / dynamic window is safe
input has zerosUse exact-count formula instead of direct window
input has negativesAvoid sliding window, use prefix sum + hashmap

The Core Templates

Fixed window (size k)
// Build first window, then slide
let sum = 0;
for (let i = 0; i < k; i++) sum += nums[i];
let maxSum = sum;

for (let i = k; i < n; i++) {
    sum += nums[i];       // add incoming right element
    sum -= nums[i - k];   // remove outgoing left element
    maxSum = Math.max(maxSum, sum);
}
Dynamic window — longest valid subarray
let i = 0, j = 0, ans = 0;
while (j < n) {
    // 1. Expand: add nums[j] to window state
    state.add(nums[j]);

    // 2. Shrink: while window is invalid, remove from left
    while (!isValid(state)) {
        state.remove(nums[i]);
        i++;
    }

    // 3. Update answer with current valid window size
    ans = Math.max(ans, j - i + 1);
    j++;
}
Count subarrays with EXACTLY k (the ans += n−j trick)
// When input has zeros, exact count can't be done by direct shrink.
// Use: count(exactly k) = count(at most k) − count(at most k−1)

const atMost = (goal) => {
    if (goal < 0) return 0;
    let i = j = ans = sum = 0;
    while (j < n) {
        sum += nums[j];
        while (sum > goal) { sum -= nums[i]; i++; }
        ans += j - i + 1;  // all subarrays ending at j with sum ≤ goal
        j++;
    }
    return ans;
};

return atMost(k) - atMost(k - 1);
ans += n − j vs ans += j − i + 1

When the window [i..j] first becomes valid, ALL extensions to the right are also valid. Use ans += n − j to count "this window + every longer window ending at or after j".

DON'T use ans += j − i + 1 in that scenario — that counts left-shrinking prefixes, a completely different set.

Input has 0s — can't direct-shrink to exact count. Use the formula.

Multi-source BFS — not sliding window, but same "enqueue all sources first" insight.

Variation Trick

The One-Line Variation Trick

Base template

// Base: count subarrays with sum exactly k
const atMost = (goal) => {
    let i = j = ans = sum = 0;
    while (j < n) {
        sum += nums[j];               // ← THIS LINE controls what we're tracking
        while (sum > goal) { sum -= nums[i]; i++; }
        ans += j - i + 1;
        j++;
    }
    return ans;
};
return atMost(k) - atMost(k - 1);

Change only one line to solve:

Count subarrays with exactly k ODD numbers

sum += nums[j] % 2 // map: odd→1, even→0

Transform values to 0/1 parity, same formula applies

Solve ↗

Longest substring with at most k distinct characters

freq[s[j]]++; if new → distinct++ // track distinct count

Change sum to distinct-character count, return max window not count

Solve ↗

Count subarrays where max appears at least k times

freq += nums[j] === max ? 1 : 0 // count max occurrences

Once freq >= k, all right-extending windows are valid: ans += n - j

Solve ↗
Sliding window checklist:
1. Can I define a window [lo, hi] with a valid/invalid state?
2. Is the state monotone — if [lo, hi] is invalid, is [lo, hi+1] also invalid? → shrink window
3. For COUNT problems: use atMost(k) - atMost(k-1) trick
4. Need max in window? → monotonic deque
5. Need sum/count in window? → simple two-pointer with running total
6. "At most k replacements/flips" → (windowSize - max_dominant_count) ≤ k
7. "Permutation in string" → fixed window + frequency matching (satisfied count)