← Index

Techniques & Patterns

Sliding Window

Keep a moving window over the data instead of recomputing from scratch.

What it is

When a problem asks about a contiguous chunk of an array/string (a 'window'), sliding it forward — adding what enters, removing what leaves — avoids recomputing the whole thing at every position. Fixed-size windows (e.g. 'sum of every 3 consecutive items') and variable-size windows (e.g. 'smallest chunk that sums to at least X') both use this, turning what looks like O(n·k) into O(n).

Real-world examples

Where you've probably used this already

Complexity

Time O(n) — each element enters and leaves the window once
Space O(k) for a fixed window, O(1) to O(n) for variable, depending on what you track

Code

// Sliding Window — keep a "window" (a start and end index) over part of
// an array/string and slide it forward, instead of recomputing from
// scratch for every possible position. Turns O(n * k) into O(n).

// --- Fixed-size window: max sum of any k consecutive elements ---
function maxSumFixedWindow(nums, k) {
  let windowSum = 0;
  for (let i = 0; i < k; i++) windowSum += nums[i]; // build the first window

  let maxSum = windowSum;
  for (let i = k; i < nums.length; i++) {
    windowSum += nums[i] - nums[i - k]; // slide: add new, drop oldest
    maxSum = Math.max(maxSum, windowSum);
  }
  return maxSum;
}

// --- Variable-size window: smallest subarray with sum >= target ---
function smallestSubarrayWithSum(nums, target) {
  let left = 0;
  let sum = 0;
  let minLength = Infinity;

  for (let right = 0; right < nums.length; right++) {
    sum += nums[right];
    while (sum >= target) {
      minLength = Math.min(minLength, right - left + 1);
      sum -= nums[left]; // shrink from the left
      left++;
    }
  }
  return minLength === Infinity ? 0 : minLength;
}

// --- Variable-size window: longest substring without repeating characters ---
function longestUniqueSubstring(str) {
  const seen = new Map(); // char -> last index seen
  let left = 0;
  let maxLength = 0;

  for (let right = 0; right < str.length; right++) {
    const char = str[right];
    if (seen.has(char) && seen.get(char) >= left) {
      left = seen.get(char) + 1; // jump window start past the repeat
    }
    seen.set(char, right);
    maxLength = Math.max(maxLength, right - left + 1);
  }
  return maxLength;
}

// --- Example Usage ---
console.log("maxSumFixedWindow([2,1,5,1,3,2], 3):", maxSumFixedWindow([2, 1, 5, 1, 3, 2], 3)); // 9
console.log(
  "smallestSubarrayWithSum([2,3,1,2,4,3], 7):",
  smallestSubarrayWithSum([2, 3, 1, 2, 4, 3], 7),
); // 2 ([4,3])
console.log("longestUniqueSubstring('abcabcbb'):", longestUniqueSubstring("abcabcbb")); // 3 ("abc")

// Real uses of this exact idea:
// - rate limiting ("max N requests per rolling 60s window")
// - rolling averages / moving averages in dashboards and charts
// - "typing search box" style debounced-lookahead over recent input