Sliding WindowArraysStringsSubarrayO(n) Optimization

Sliding Window — The Complete Guide

Master the Sliding Window pattern from first principles. Learn to recognize it instantly, evolve brute-force into optimal solutions, and confidently solve interview questions.

40 min read10 sections
01

Intuition from Scratch

Why does this pattern exist?

Imagine you're looking at a long row of houses from a train window. The window shows you a few houses at a time. As the train moves, old houses slide out of view on the left and new ones slide in on the right. You never need to look at the entire row at once — just the current window.

That's exactly what the Sliding Window pattern does with arrays and strings. Instead of checking every possible subarray (O(n²) or worse), you maintain a "window" that slides across the data. You add one element on the right, remove one on the left, and update your answer — all in O(1) per step. The total work becomes O(n).

The pattern exists because many problems ask about contiguous subarrays or substrings with some constraint. The brute-force approach checks every start/end pair, but the sliding window exploits the fact that consecutive windows overlap — most of the data is the same, so you only need to process the change.

Analogies to Build Intuition

🚂

The Train Window

You're on a train looking through a fixed-size window. As the train moves, you see new scenery on one side and lose it on the other. You never need to look at the entire landscape — just what's in the window right now. That's a fixed-size sliding window.

🔍

The Magnifying Glass

Imagine sliding a magnifying glass across a line of text. You can stretch or shrink the glass to find the smallest section that contains all the letters you need. That's a variable-size sliding window — expand until you satisfy the condition, then shrink to optimize.

📊

The Moving Average

Stock charts show a 'moving average' — the average price over the last k days. Each day, you add the new price and drop the oldest one. You don't recalculate from scratch every day. That's the core efficiency of sliding window.

What kind of problems does it solve?

Sliding Window is your go-to when:

  • You need the maximum/minimum sum of k consecutive elements
  • You need the longest/shortest substring satisfying a condition
  • You need to count subarrays with a specific property
  • The input is linear (array or string) and the answer involves a contiguous range
  • You want to reduce O(n²) brute force to O(n)

The core insight

Sliding Window works because consecutive windows share most of their elements. When you slide the window one step right, you only need to process the one element entering and the one element leaving — not the entire window. This turns O(n × k) into O(n).

02

Pattern Recognition

Recognizing Sliding Window in the wild is the most important skill. Here are the signals to look for and the traps to avoid.

Keywords & Signals in the Problem Statement

Use Sliding Window when you see...

  • "Maximum/minimum sum of k consecutive elements"
  • "Longest substring with at most k distinct characters"
  • "Shortest subarray with sum ≥ target"
  • "Contiguous subarray" with a constraint
  • "Substring containing all characters of T"
  • "Maximum number of vowels in a substring of length k"
  • "Longest repeating character replacement"
  • "Subarray product less than k"
  • Linear data (array or string) + contiguous range
  • O(n) time hint with subarray/substring focus

Don't use Sliding Window when...

  • Elements don't need to be contiguous — consider DP or subsequence approaches
  • You need pairs from opposite ends — use Two Pointers
  • The problem involves cumulative sums over arbitrary ranges — use Prefix Sum
  • You need to find a specific pair/triplet — use Two Pointers or Hashing
  • The data structure is a tree or graph — different traversal needed
  • Order doesn't matter — Hashing or Sorting may be simpler

Sliding Window vs. Similar Patterns

PatternWhen to UseKey Difference
Sliding WindowContiguous subarray/substring with constraintBoth pointers move right; window expands/shrinks to maintain condition
Two PointersSorted data, pairs, in-place operationsPointers converge from opposite ends based on comparison
Prefix SumRange sum queries, subarray sum equals kPrecomputes cumulative sums; answers arbitrary range queries in O(1)
HashingUnsorted data, need O(1) lookupTrades space for time; no pointer movement, just map lookups

The contiguous test

If the problem says "subarray" or "substring" (contiguous) and involves optimizing over all possible windows, it's almost certainly Sliding Window. If it says "subsequence" (not contiguous), it's probably DP.

03

Brute Force → Optimized Thinking

Let's walk through the thinking evolution using a concrete example: Maximum Sum Subarray of Size K — given an array and integer k, find the maximum sum of any contiguous subarray of size k.

1

Brute Force — Recompute Every Window

O(n × k) time · O(1) space

For each starting index, sum the next k elements. Track the maximum. This works but recomputes the entire sum for each window — wasteful because consecutive windows share k-1 elements.

Brute Forcetypescript
function maxSumBrute(nums: number[], k: number): number {
  let maxSum = -Infinity;
  for (let i = 0; i <= nums.length - k; i++) {
    let windowSum = 0;
    for (let j = i; j < i + k; j++) {
      windowSum += nums[j];
    }
    maxSum = Math.max(maxSum, windowSum);
  }
  return maxSum;
}
// Problem: We're re-adding k-1 elements that were already in the previous window.
2

Optimal — Slide the Window

O(n) time · O(1) space

Compute the sum of the first window. Then slide: add the new element entering on the right, subtract the element leaving on the left. Each step is O(1), total is O(n).

Sliding Window — Optimaltypescript
function maxSumSliding(nums: number[], k: number): number {
  // Compute first window
  let windowSum = 0;
  for (let i = 0; i < k; i++) {
    windowSum += nums[i];
  }
  let maxSum = windowSum;

  // Slide the window
  for (let i = k; i < nums.length; i++) {
    windowSum += nums[i];       // add new element entering
    windowSum -= nums[i - k];   // remove element leaving
    maxSum = Math.max(maxSum, windowSum);
  }

  return maxSum;
}
// Perfect: O(n) time, O(1) space. Each element is processed exactly once.

Why Does the Optimization Work?

1

Consecutive windows overlap

Window [i..i+k-1] and window [i+1..i+k] share k-1 elements. Only one element leaves (index i) and one enters (index i+k). Recomputing the entire sum wastes work on the shared elements.

2

Incremental update replaces recomputation

Instead of summing k elements each time, we adjust the previous sum: newSum = oldSum + entering - leaving. This is O(1) per slide instead of O(k).

3

The general principle

Any aggregate (sum, count, frequency map, max/min) that can be incrementally updated when one element enters and one leaves is a candidate for sliding window. The key question: can I maintain the window state in O(1) per step?

The thinking process matters more than the code

In interviews, walking through this brute → optimal progression shows the interviewer how you think. Even if you know the optimal solution immediately, briefly mention the brute force and explain why you're optimizing.

04

Core Templates

Sliding Window has two main templates. Memorize these — most problems are variations of one of them.

Template 1: Fixed-Size Window

The window size is given (k). You always add one element and remove one. Used for "max sum of k elements," "average of k elements," etc.

Fixed-Size Window Templatetypescript
function fixedWindow(arr: number[], k: number): Result {
  // 1. Build the first window
  let windowState = initializeWith(arr[0..k-1]);

  // 2. Record answer for first window
  let answer = evaluate(windowState);

  // 3. Slide: add right, remove left
  for (let right = k; right < arr.length; right++) {
    const left = right - k;
    windowState.add(arr[right]);      // element entering
    windowState.remove(arr[left]);    // element leaving
    answer = best(answer, evaluate(windowState));
  }

  return answer;
}
// When to use: "k consecutive elements", "window of size k"
// Key: window size never changes — always exactly k elements

Template 2: Variable-Size Window (Expand & Shrink)

The window size changes dynamically. Expand right until a condition is met, then shrink from the left to optimize. Used for "longest/shortest substring with condition X."

Variable-Size Window Templatetypescript
function variableWindow(arr: number[]): Result {
  let left = 0;
  let windowState = {};  // frequency map, sum, count, etc.
  let answer = initialValue;

  for (let right = 0; right < arr.length; right++) {
    // 1. Expand: add arr[right] to window state
    windowState.add(arr[right]);

    // 2. Shrink: while window is INVALID, remove from left
    while (!isValid(windowState)) {
      windowState.remove(arr[left]);
      left++;
    }

    // 3. Update answer (window [left..right] is valid)
    answer = best(answer, right - left + 1);
  }

  return answer;
}
// When to use: "longest/shortest subarray/substring with condition"
// Key: right always moves forward; left only moves forward (never back)
// This guarantees O(n) — each element enters and leaves at most once

Template 2b: Shrink to Find Minimum

When you need the shortest valid window, flip the logic: expand until valid, then shrink while still valid, recording the minimum.

Minimum Window Templatetypescript
function minWindow(arr: number[]): Result {
  let left = 0;
  let windowState = {};
  let answer = Infinity;

  for (let right = 0; right < arr.length; right++) {
    windowState.add(arr[right]);

    // Shrink while window is VALID (to find minimum)
    while (isValid(windowState)) {
      answer = Math.min(answer, right - left + 1);
      windowState.remove(arr[left]);
      left++;
    }
  }

  return answer;
}
// When to use: "minimum window substring", "shortest subarray with sum ≥ target"
// Key difference: shrink while VALID (not while invalid)

Which template to pick?

Ask yourself: (1) Is the window size fixed? → Template 1. (2) Do I need the longest valid window? → Template 2 (shrink while invalid). (3) Do I need the shortest valid window? → Template 2b (shrink while valid).

05

Variations & Sub-Patterns

Sliding Window isn't a single trick — it's a family of techniques. Here are the most common variations.

VariationWindow BehaviorClassic Example
Fixed-size sum/averageWindow size = k, slide one step at a timeMax Sum Subarray of Size K, Max Avg Subarray
Longest with at most K distinctExpand right; shrink left when distinct count > kLongest Substring with At Most K Distinct Characters
Longest without repeatingExpand right; shrink left when duplicate foundLongest Substring Without Repeating Characters
Minimum window containingExpand until all required chars found; shrink to minimizeMinimum Window Substring
Subarray product/sum constraintExpand right; shrink left when product/sum exceeds limitSubarray Product Less Than K
Character replacementWindow valid when (size - maxFreq) ≤ k replacementsLongest Repeating Character Replacement
Count of valid subarraysFor each right, count valid windows ending at rightNumber of Subarrays with Bounded Maximum
Sliding window + dequeDeque maintains max/min candidates within the windowSliding Window Maximum

Problems mentioned above

Deep Dive: Frequency Map as Window State

Many sliding window problems use a hash map to track character/element frequencies within the window. The map is the "window state" — you add to it when expanding and remove from it when shrinking.

Frequency Map Window — Longest Substring Without Repeatingtypescript
function lengthOfLongestSubstring(s: string): number {
  const freq = new Map<string, number>();
  let left = 0;
  let maxLen = 0;

  for (let right = 0; right < s.length; right++) {
    // Expand: add s[right]
    freq.set(s[right], (freq.get(s[right]) ?? 0) + 1);

    // Shrink: while we have a duplicate
    while (freq.get(s[right])! > 1) {
      freq.set(s[left], freq.get(s[left])! - 1);
      if (freq.get(s[left]) === 0) freq.delete(s[left]);
      left++;
    }

    // Update answer
    maxLen = Math.max(maxLen, right - left + 1);
  }

  return maxLen;
}
// Time: O(n) — each char enters and leaves the map at most once
// Space: O(min(n, alphabet)) — map holds at most the alphabet size

Deep Dive: The "Replacements Allowed" Trick

In problems like "Longest Repeating Character Replacement," the window is valid when the number of characters you'd need to replace is ≤ k. The formula: replacements needed = window size − frequency of the most common character.

Character Replacement — Replacements ≤ ktypescript
function characterReplacement(s: string, k: number): number {
  const freq = new Map<string, number>();
  let left = 0;
  let maxFreq = 0; // max frequency of any single char in current window
  let maxLen = 0;

  for (let right = 0; right < s.length; right++) {
    freq.set(s[right], (freq.get(s[right]) ?? 0) + 1);
    maxFreq = Math.max(maxFreq, freq.get(s[right])!);

    // Window is invalid if we need more than k replacements
    // replacements = windowSize - maxFreq
    while ((right - left + 1) - maxFreq > k) {
      freq.set(s[left], freq.get(s[left])! - 1);
      left++;
    }

    maxLen = Math.max(maxLen, right - left + 1);
  }

  return maxLen;
}
// Key insight: we don't need to update maxFreq when shrinking.
// A smaller maxFreq can only shrink the window, never grow it.
// So the answer is still correct — we just skip suboptimal windows.
06

Visual Walkthrough

Let's trace through "Longest Substring Without Repeating Characters" step by step. Input: "abcabcbb".

Longest Substring Without Repeating — Step-by-Steptext
String: a  b  c  a  b  c  b  b
Index:  0  1  2  3  4  5  6  7

Window state: Set of characters in current window
Goal: longest window with all unique characters

Step 1: right=0, char='a'Set={a}         window="a"       len=1  max=1
Step 2: right=1, char='b'Set={a,b}       window="ab"      len=2  max=2
Step 3: right=2, char='c'Set={a,b,c}     window="abc"     len=3  max=3
Step 4: right=3, char='a''a' in Set!
        Shrink: remove 'a', left=1          Set={b,c}
        Add 'a'                             Set={b,c,a}      window="bca"     len=3  max=3
Step 5: right=4, char='b''b' in Set!
        Shrink: remove 'b', left=2          Set={c,a}
        Add 'b'                             Set={c,a,b}      window="cab"     len=3  max=3
Step 6: right=5, char='c''c' in Set!
        Shrink: remove 'c', left=3          Set={a,b}
        Add 'c'                             Set={a,b,c}      window="abc"     len=3  max=3
Step 7: right=6, char='b''b' in Set!
        Shrink: remove 'a', left=4          Set={b,c}  — still has 'b'!
        Shrink: remove 'b', left=5          Set={c}
        Add 'b'                             Set={c,b}        window="cb"      len=2  max=3
Step 8: right=7, char='b''b' in Set!
        Shrink: remove 'c', left=6          Set={b}  — still has 'b'!
        Shrink: remove 'b', left=7          Set={}
        Add 'b'                             Set={b}          window="b"       len=1  max=3

Answer: 3 ("abc")

Key observations:
- right always moves forward (one step per iteration)
- left only moves forward (never backwards)
- Each character enters the set once and leaves onceO(n) total
- The shrink loop may run multiple times in one step, but total shrinks across ALL stepsn

Minimum Window Substring — The Harder Variant

Input: s = "ADOBECODEBANC", t = "ABC". Find the shortest substring of s containing all characters of t.

Minimum Window Substring — Tracetext
s: A  D  O  B  E  C  O  D  E  B  A  N  C
   0  1  2  3  4  5  6  7  8  9  10 11 12

Need: {A:1, B:1, C:1}  (3 unique chars to satisfy)
formed = 0 (how many chars are fully satisfied)

Expand right until formed === 3:
  right=0 'A'have={A:1} → formed=1
  right=1 'D'have={A:1,D:1}
  right=2 'O'have={A:1,D:1,O:1}
  right=3 'B'have={A:1,D:1,O:1,B:1} → formed=2
  right=4 'E'have={...,E:1}
  right=5 'C'have={...,C:1} → formed=3window="ADOBEC" len=6

Shrink left while formed === 3:
  left=0 remove 'A'have={A:0} → formed=2STOP
  Best so far: "ADOBEC" (len=6, start=0)

Continue expanding...
  right=9  'B'formed=2 still
  right=10 'A'formed=3window="CODEBA"wait, let me shrink...
  
  Shrink: left=1 'D', left=2 'O', left=3 'B'formed=2STOP
  Best: "BANC" is found later at right=12

Final answer: "BANC" (length 4, starting at index 9)

The pattern: expand until validshrink while validrecord minimumrepeat
07

Practice Problems

These 7 problems are carefully ordered from easy to hard. Each one teaches a different aspect of Sliding Window. Solve them in order.

1

LC 643. Maximum Average Subarray I

Easy

Why this pattern:

Fixed-size sliding window. Maintain a running sum of k elements — slide right by adding the new element and subtracting the one that falls off. The simplest possible sliding window problem.

Key idea: Compute sum of first k elements. Slide one step at a time: sum += nums[right] - nums[right - k]. Track max sum, divide by k at the end.

2

LC 3. Longest Substring Without Repeating Characters

Medium

Why this pattern:

Variable-size window with a hash set/map. Expand right; when a duplicate is found, shrink from the left until the window is valid again.

Key idea: Use a Set to track characters in the window. When s[right] is already in the set, remove s[left] and advance left until the duplicate is gone. Track max window size.

3

LC 1004. Max Consecutive Ones III

Medium

Why this pattern:

Variable-size window counting zeros. The window is valid when the number of zeros ≤ k. Expand right; shrink left when zero count exceeds k.

Key idea: Count zeros in the window. When zeroCount > k, shrink from left (if nums[left] === 0, decrement zeroCount). The longest valid window is the answer.

4

LC 567. Permutation in String

Medium

Why this pattern:

Fixed-size window (size = s1.length) with frequency matching. Slide across s2 and check if the window's character frequencies match s1's frequencies.

Key idea: Build a frequency map for s1. Slide a window of size s1.length across s2, maintaining a frequency map. When the maps match, return true. Use a 'matches' counter to avoid comparing full maps each step.

5

LC 424. Longest Repeating Character Replacement

Medium

Why this pattern:

Variable-size window where validity = (windowSize - maxFreq) ≤ k. Track the frequency of each character and the max frequency in the window.

Key idea: The number of replacements needed = windowSize - frequency of the most common character. If this exceeds k, shrink from left. The answer is the largest valid window.

6

LC 76. Minimum Window Substring

Hard

Why this pattern:

Variable-size window (find minimum). Expand until all characters of t are covered, then shrink while still valid to find the smallest window.

Key idea: Use a frequency map for t and a 'formed' counter tracking how many unique characters are satisfied. Expand right until formed === required. Then shrink left while still valid, updating the minimum window.

7

LC 239. Sliding Window Maximum

Hard

Why this pattern:

Fixed-size window + monotonic deque. The deque maintains indices of decreasing values — the front is always the current window's maximum.

Key idea: Maintain a deque of indices with decreasing values. For each new element: remove expired indices from front, remove smaller values from back, push current index. Front of deque = window max.

Practice strategy

For each problem: (1) Identify fixed vs variable window before coding. (2) Decide what the "window state" is (sum? frequency map? count?). (3) Write the expand/shrink logic. (4) After solving, write one sentence: "This is [fixed/variable] window because [signal]."

08

Common Mistakes

These are the traps that catch people on Sliding Window problems. Learn them here so you don't learn them in an interview.

🔄

Forgetting to remove the leaving element

You add the new element to the window state but forget to remove the element that's sliding out. The window grows unbounded and your answer is wrong.

Always pair every 'add' with a 'remove'. In fixed windows: remove arr[right - k]. In variable windows: remove arr[left] inside the shrink loop.

📏

Off-by-one in window size

Using right - left instead of right - left + 1 for window size, or starting the slide loop at the wrong index. This gives answers that are off by one.

Window size = right - left + 1 (inclusive on both ends). For fixed windows, the slide loop starts at index k (not k-1). Test with a 1-element window to verify.

🔀

Using Sliding Window on non-contiguous problems

You see 'longest subsequence' and reach for Sliding Window, but subsequences aren't contiguous. The window approach breaks because you can't skip elements.

Sliding Window only works on contiguous subarrays/substrings. If the problem says 'subsequence', use DP or greedy instead.

🧮

Not cleaning up the frequency map

When a character's count drops to 0, you leave it in the map. This causes issues when checking map size (distinct count) — the map reports more distinct characters than actually exist.

When decrementing a frequency to 0, delete the key from the map: if (freq.get(ch) === 0) freq.delete(ch). This keeps map.size accurate.

Confusing 'shrink while invalid' vs 'shrink while valid'

For longest window problems you shrink while invalid (to restore validity). For shortest window problems you shrink while valid (to minimize). Mixing these up inverts your answer.

Longest → shrink while INVALID. Shortest → shrink while VALID. Write a comment above your while loop stating which one you're using.

🏃

Moving left pointer backwards

In some edge cases, you accidentally move left backwards (left--) or reset it to 0. This breaks the O(n) guarantee and can cause infinite loops.

The left pointer should ONLY move forward (left++). Never decrement it. If you feel the need to move it back, your window state tracking is wrong — fix the state, not the pointer.

09

Interview Strategy

Knowing the pattern is half the battle. Here's how to present it in an interview setting to maximize your score.

The 5-Step Interview Flow

1

Clarify & Confirm

'The input is a string/array? I need the longest/shortest contiguous subarray/substring? Are there constraints on the window (at most k distinct, sum ≥ target)?' — Clarifying 'contiguous' is the key signal.

2

State the Brute Force

'The brute force would check every possible subarray — that's O(n²). But since consecutive windows overlap, I can maintain a running state and slide the window in O(n).' — Name the optimization.

3

Identify Window Type

'This is a [fixed/variable]-size window. The window state is [sum/frequency map/count]. The validity condition is [state description].' — Be explicit about the window mechanics.

4

Code It Clean

Write the solution using the template. Name variables clearly: left, right, windowSum/freq. Add a comment for the shrink condition. Keep the expand-shrink-update structure visible.

5

Test & Analyze

Trace through a small example showing the window expanding and shrinking. State complexity: 'O(n) time because each element enters and leaves the window at most once. O(k) or O(1) space.'

Common Follow-Up Questions

Follow-UpHow to Handle
"What if the window size isn't fixed?"Switch to variable-size template. Define the validity condition and use expand/shrink.
"Can you find the shortest instead of longest?"Flip the shrink logic: shrink while VALID instead of while INVALID. Update answer inside the shrink loop.
"What if elements can be negative?"Sliding window on sums breaks with negatives (adding an element can decrease the sum). Use Prefix Sum + monotonic deque or DP instead.
"Can you return the actual subarray, not just the length?"Track the start index of the best window alongside the length. Return arr.slice(bestStart, bestStart + bestLen).
"What about counting ALL valid subarrays?"For each right, the number of valid windows ending at right is (right - left + 1). Sum these up.
"Can you do it in O(1) space?"If the window state is a frequency map over a fixed alphabet (e.g., 26 lowercase letters), it's already O(1) space. Mention this.

The meta-skill

Interviewers aren't just testing if you know Sliding Window. They're testing if you can (1) recognize the "contiguous + constraint" signal, (2) choose fixed vs variable, and (3) adapt when the problem changes. Practice explaining your window state and validity condition out loud.

10

Summary & Cheat Sheet

Pin this section. Review it before mock interviews and contests.

Quick Revision Cheat Sheet

Core idea: Maintain a window over contiguous elements; slide it to avoid recomputation — O(n²) → O(n)

Fixed window: Size = k. Add right, remove left. Used for: max sum of k, average of k, permutation check

Variable window (longest): Expand right always. Shrink left while INVALID. Track max(right - left + 1)

Variable window (shortest): Expand right always. Shrink left while VALID. Track min(right - left + 1)

Window state: Sum, frequency map, count, or deque — whatever lets you check validity in O(1)

O(n) guarantee: Each element enters the window once and leaves once. Total work = 2n = O(n)

Frequency map cleanup: Delete keys when count reaches 0 to keep map.size accurate

Negatives break sums: Sliding window on sums assumes adding elements increases the sum. Negatives violate this — use prefix sum instead

Contiguous only: Sliding window = subarray/substring (contiguous). Subsequence (non-contiguous) = DP

Mental trigger: "Longest/shortest subarray/substring with constraint" → Sliding Window

Decision Flowchart

When to Use Sliding Window — Decision Treetext
Is the problem about a CONTIGUOUS subarray or substring?
├── NONot Sliding Window. Consider DP, Backtracking, or Hashing.
└── YESIs the window size fixed (given k)?
          ├── YESFixed-Size Window (Template 1)
Add right, remove left, update answer each step.
          └── NODo you need the LONGEST or SHORTEST valid window?
                    ├── LONGESTVariable Window: shrink while INVALID
Track max(right - left + 1)
                    └── SHORTESTVariable Window: shrink while VALID
                                  Track min(right - left + 1)

What's the window state?
├── Sum/countsingle variable, O(1) update
├── Character frequencieshash map, O(1) update per char
├── Distinct counthash map + map.size
└── Max/min in windowmonotonic deque (advanced)

One last thing

Sliding Window is one of the highest-ROI patterns for interviews. It appears in easy problems (Max Average Subarray) and hard ones (Minimum Window Substring). The difference isn't the technique — it's recognizing the "contiguous + constraint" signal and choosing the right template. Solve the 7 practice problems in Section 07, and you'll have that intuition locked in.