Techniques

Sliding Window

Track a moving range — convert O(n²) subarray problems to O(n)

The sliding window technique maintains a contiguous range over an array or string, expanding the right edge and shrinking the left edge based on a condition. Each element enters and leaves the window at most once, giving O(n) total work for problems that brute force would solve in O(n × k) or O(n²). Used for substring problems, k-window aggregates, and rate-limiting algorithms.

  • TimeO(n) — each element processed at most twice
  • SpaceO(window contents)
  • Two flavorsFixed-size window, variable-size window
  • Vs brute forceO(n × k) → O(n) for fixed-size; O(n²) → O(n) for variable
  • Famous problemsLongest substring no-repeat, max k-subarray, min window substring
  • Used inRate limiters, streaming statistics, time-series aggregation

Interactive visualization

Press play, or step through manually. The visualization is yours to drive — try it before reading on.

Open visualization fullscreen ↗

Watch the 60-second explainer

A condensed visual walkthrough — narrated, captioned, under a minute.

How sliding window works

Maintain two indices, left and right, defining the window [left, right]. The right edge advances every iteration. The left edge advances when the window violates some condition.

for right in range(n):
    add arr[right] to window
    while window violates constraint:
        remove arr[left] from window
        left += 1
    update answer using current window

Total work — each element is added once (right advances n times) and removed at most once (left advances ≤ n times). O(n) amortized regardless of how often the inner shrinking loop iterates.

Fixed-size vs variable-size windows

Fixed-size (size k)Variable-size
Window sizeAlways exactly kDepends on a constraint
Left edgeAlways lags k positions behind rightAdvances only when constraint violated
Common questions"What's the max/min/sum/avg over any k-window?""Longest/shortest subarray satisfying X"
ExamplesMoving average, max k-subarray, max k-slidingLongest no-repeat substring, min window substring
Code complexity~10 lines~20 lines

Fixed-window: maximum sum of any k consecutive elements

function maxSumK(arr, k) {
  if (arr.length < k) return null;
  // Sum of first k
  let windowSum = 0;
  for (let i = 0; i < k; i++) windowSum += arr[i];
  let maxSum = windowSum;
  // Slide
  for (let i = k; i < arr.length; i++) {
    windowSum += arr[i] - arr[i - k];  // add new, remove old
    if (windowSum > maxSum) maxSum = windowSum;
  }
  return maxSum;
}

The brute force recomputes each window's sum in O(k); total O(n × k). Sliding window updates incrementally — O(1) per shift, O(n) total.

Variable-window: longest substring with no repeating characters

function longestUniqueSubstring(s) {
  const seen = new Map();   // char → last index seen
  let left = 0, best = 0;
  for (let right = 0; right < s.length; right++) {
    const c = s[right];
    if (seen.has(c) && seen.get(c) >= left) {
      left = seen.get(c) + 1;  // skip past previous occurrence
    }
    seen.set(c, right);
    if (right - left + 1 > best) best = right - left + 1;
  }
  return best;
}

// "abcabcbb" → 3 ("abc")
// "bbbbb"    → 1 ("b")
// "pwwkew"   → 3 ("wke")

Each character is added to the map once and may cause a left jump once. O(n) total despite the inner conditional.

Variable-window: minimum window substring (LeetCode 76)

Given strings s and t, find the smallest window in s containing all characters of t (with multiplicities). The classic hard sliding-window problem.

function minWindow(s, t) {
  if (s.length < t.length) return '';
  const need = new Map();
  for (const c of t) need.set(c, (need.get(c) || 0) + 1);
  let needCount = need.size;

  const have = new Map();
  let haveCount = 0;
  let left = 0, bestLen = Infinity, bestL = 0;

  for (let right = 0; right < s.length; right++) {
    const c = s[right];
    if (need.has(c)) {
      have.set(c, (have.get(c) || 0) + 1);
      if (have.get(c) === need.get(c)) haveCount++;
    }
    // Shrink from left while constraint still holds
    while (haveCount === needCount) {
      if (right - left + 1 < bestLen) {
        bestLen = right - left + 1;
        bestL = left;
      }
      const lc = s[left];
      if (need.has(lc)) {
        have.set(lc, have.get(lc) - 1);
        if (have.get(lc) < need.get(lc)) haveCount--;
      }
      left++;
    }
  }
  return bestLen === Infinity ? '' : s.slice(bestL, bestL + bestLen);
}

Sliding window for streaming and rate limiting

Time-windowed analytics use the same pattern but indexed by timestamp instead of array position. A rate limiter counting requests per minute:

class SlidingWindowRateLimiter {
  constructor(limit, windowMs) {
    this.limit = limit;
    this.windowMs = windowMs;
    this.timestamps = new Map();  // userId → array of timestamps
  }

  allow(userId) {
    const now = Date.now();
    const cutoff = now - this.windowMs;
    let arr = this.timestamps.get(userId) || [];
    // Drop expired entries (slide window)
    while (arr.length && arr[0] <= cutoff) arr.shift();
    if (arr.length >= this.limit) {
      this.timestamps.set(userId, arr);
      return false;
    }
    arr.push(now);
    this.timestamps.set(userId, arr);
    return true;
  }
}

Note — arr.shift() is O(n) in JavaScript. Production rate limiters use deques or sliding-counter approximations to keep operations O(1).

When to use sliding window

  • Subarray-with-property questions. "Longest contiguous subarray with sum ≤ X," "longest substring with at most K distinct characters," "minimum window containing all chars of T." If the property is monotonic in window size, sliding window applies.
  • Fixed-size window aggregates. Moving averages, k-element sums, max-of-k-window. Standard time-series operation.
  • Rate limiting. "At most N actions in the last W seconds." Token bucket, leaky bucket, sliding window — all variants of the same core idea.
  • Streaming analytics. Rolling top-k, recent unique users, anomaly detection. Window-indexed by event time.
  • Two-pointer-on-string problems. Often the most natural framing.

When sliding window fails

  • Non-monotonic constraints. "Longest subarray with sum exactly X" — extending can over- or under-shoot. Use prefix sums + hash map (find pair of prefix sums whose difference is X).
  • Negative numbers in "sum at most X" problems. Adding a negative number can satisfy a previously-violated constraint, breaking the monotonic assumption. Use prefix sums or alternative approaches.
  • Non-contiguous subsets. Sliding window only works for contiguous subarrays. "Subset with sum X" needs DP, not windowing.
  • Tiny n where O(n²) is fine. The sliding-window logic adds overhead; for n < 100 the constant factors of brute force often win.

Common sliding window bugs

  • Off-by-one in window size. Window is [left, right] inclusive — size is right - left + 1. Forgetting the +1 produces "longest" answers that are 1 too short.
  • Not advancing left in the inner shrink loop. Forgot a left++ at the end of the inner loop = infinite loop.
  • Wrong shrink condition. While the constraint is violated, shrink. If only shrinks once — leaves the window in an invalid state if multiple shrinks are needed.
  • Forgetting to update the answer in the right place. For longest-with-property, update inside the shrink-while loop. For longest-without-property, update outside. Confusing the two leads to off-by-one or wrong answers.
  • Using sliding window on non-monotonic constraints. Some questions look like sliding-window targets but aren't ("subarray with sum exactly X"). Verify monotonicity; if not, use prefix sums + hash map or DP.
  • Slow inner-data-structure operations. If the window is a Map and you call .delete() and .set() per shift, that's fine. If it's an array and you use .shift() per shift, that's O(n²). Use a deque or maintain head index.

Frequently asked questions

When should I use sliding window vs brute force?

When the brute force computes overlapping aggregates over consecutive ranges. A sum, count, or frequency map of arr[i..i+k] reuses most of the data from arr[i-1..i+k-1]. Sliding window adds the new entering element and subtracts the leaving one — O(1) per shift instead of O(k). Total: O(n) instead of O(n × k).

What's the difference between two-pointers and sliding window?

Sliding window is a specific case of two-pointers where both pointers move only forward and the region between them is the "window" being maintained. Two-pointers is the broader technique — pointers can move in any direction, at any rate. Sliding window is the specialization for "subarray with property X" problems.

How do you decide when to shrink the window?

When the window violates the constraint. For "longest substring with at most k distinct characters" — when distinct count exceeds k, advance the left edge until it drops back to k or below. The right edge advances every iteration; the left edge advances when needed. Each pointer moves O(n) total.

What's a fixed-size window?

The window is exactly k elements wide. Right edge advances; left edge follows k positions behind. Used for "max sum of k consecutive elements," "moving average," "k-subarray statistics." No expansion / contraction logic — just slide.

What's a variable-size window?

Window size depends on a condition. Right edge advances unconditionally; left edge advances only when needed (e.g., constraint violated, or shrinking would still satisfy the constraint). Used for "longest/shortest substring with property X." Most challenging sliding window problems are variable-size.

How does the technique work for streaming data?

A "tumbling" or "sliding" time window in stream processing. Each event has a timestamp; the window holds events with timestamp ≥ now − duration. Add events as they arrive; remove events older than the window. Used in rate limiting, anomaly detection, and real-time analytics. Same algorithm, time-indexed instead of array-indexed.

Can sliding window solve any subarray problem?

Only when the property is monotonic — extending the window doesn't ever fix a violation, so when violated you must shrink. "Longest subarray with sum ≤ X (positive elements)" works. "Longest subarray with sum exactly X" doesn't (extending can fix or worsen the sum). For non-monotonic properties, fall back to prefix sums + hash map or DP.