Skip to main content
Guides Skills and frameworks Sliding Window LeetCode Pattern Guide — Fixed and Variable Window Templates
Skills and frameworks

Sliding Window LeetCode Pattern Guide — Fixed and Variable Window Templates

9 min read · April 25, 2026

A template-driven guide to fixed and variable sliding window problems, covering invariants, frequency maps, at-most-K counting, replacement tricks, deques, and common traps.

Sliding Window LeetCode Pattern Guide — Fixed and Variable Window Templates

A sliding window LeetCode pattern guide should help you decide when two pointers are enough, what invariant the window maintains, and exactly when to expand or shrink. Fixed-window problems ask for the best value over every length-k range. Variable-window problems ask for the longest, shortest, or count of subarrays/substrings that satisfy a constraint. The code is short only after the invariant is clear.

This guide gives templates, recognition rules, traps, and interview narration for fixed and variable window problems.

Sliding window LeetCode pattern guide: when to use it

Sliding window works when you are scanning a contiguous segment of an array or string and can update the answer as the segment moves. The important word is contiguous. Subsequences are not sliding-window problems because they can skip elements. Sorting problems are usually not sliding-window problems unless the sort creates a meaningful contiguous order.

Clues in the prompt:

  • subarray or substring
  • longest, shortest, maximum, minimum, or count over contiguous ranges
  • at most K distinct characters
  • no repeating characters
  • sum at least target with positive numbers
  • average of every window of size K
  • permutation in string or anagram indices
  • replace at most K characters

Sliding window usually needs a property that changes predictably when you move left or right. If all numbers are positive, expanding increases sum and shrinking decreases sum. If characters are counted, expanding increments a frequency and shrinking decrements it. If negative numbers appear in a sum problem, a basic sliding window may fail because sum is no longer monotonic.

A good interview opener: "This is a contiguous-range problem, so I will maintain a window [left, right] and an invariant that tells me when the window is valid. Then I can update the answer without rechecking the whole window."

Fixed window template

Use a fixed window when the length is given: size k, every group of k, average of k, maximum sum of k, or count of matches in a length equal to another string.

Template:

def fixed_window(nums, k):
    window = 0
    best = float('-inf')

    for right, x in enumerate(nums):
        window += x

        if right >= k:
            window -= nums[right - k]

        if right >= k - 1:
            best = max(best, window)

    return best

The invariant: after removing the old element, window represents the last k elements ending at right. The update happens only once the first full window exists.

Example: maximum average subarray of length k is the same template with best / k at the end. Counting vowels in every length-k substring uses a boolean contribution instead of numeric sum. Checking if a string contains a permutation of another string uses a fixed window whose length equals the pattern length, plus character counts.

Fixed-window mistakes:

  • updating the answer before the window reaches size k
  • removing the left element too late
  • using right - k + 1 incorrectly
  • recomputing sum/count from scratch for every window
  • forgetting edge cases when k > len(nums) or k == 0

Variable window template: longest valid window

Use variable window when you want the longest substring/subarray satisfying a condition: no repeats, at most K distinct, at most K replacements, sum <= target with positive numbers.

Template:

def longest_valid(s):
    left = 0
    best = 0
    state = {}

    for right, ch in enumerate(s):
        add ch to state

        while window is invalid:
            remove s[left] from state
            left += 1

        best = max(best, right - left + 1)

    return best

The invariant after the while loop: the current window is valid. Because it is valid, it can update the longest answer.

For Longest Substring Without Repeating Characters:

def length_of_longest_substring(s):
    left = 0
    seen = set()
    best = 0

    for right, ch in enumerate(s):
        while ch in seen:
            seen.remove(s[left])
            left += 1
        seen.add(ch)
        best = max(best, right - left + 1)
    return best

For Longest Substring with At Most K Distinct Characters:

from collections import defaultdict

def longest_k_distinct(s, k):
    left = 0
    count = defaultdict(int)
    best = 0

    for right, ch in enumerate(s):
        count[ch] += 1
        while len(count) > k:
            old = s[left]
            count[old] -= 1
            if count[old] == 0:
                del count[old]
            left += 1
        best = max(best, right - left + 1)
    return best

In both cases, every character enters and leaves the window at most once, so time is O(n).

Variable window template: shortest valid window

For shortest-window problems, the order flips. You expand until the window becomes valid, then shrink while it remains valid, updating the answer during shrink.

Template:

def shortest_valid(arr):
    left = 0
    best = float('inf')
    state = initialize

    for right, x in enumerate(arr):
        add x to state

        while window is valid:
            best = min(best, right - left + 1)
            remove arr[left] from state
            left += 1

    return best if best != float('inf') else 0

Example: minimum size subarray sum with positive numbers.

def min_subarray_len(target, nums):
    left = 0
    total = 0
    best = float('inf')

    for right, x in enumerate(nums):
        total += x
        while total >= target:
            best = min(best, right - left + 1)
            total -= nums[left]
            left += 1

    return 0 if best == float('inf') else best

Why positivity matters: if numbers can be negative, shrinking may increase the sum or expanding may decrease it. Then this invariant breaks and you may need prefix sums plus a hashmap or monotonic deque.

Frequency maps and anagram windows

String window problems often use frequency maps. The trick is to avoid comparing full dictionaries on every step when the alphabet is large. Keep a matches count or a need count.

For finding anagrams of p in s, use a fixed window of length len(p). A straightforward version is acceptable for lowercase English letters:

from collections import Counter

def find_anagrams(s, p):
    need = Counter(p)
    window = Counter()
    ans = []
    k = len(p)

    for right, ch in enumerate(s):
        window[ch] += 1
        if right >= k:
            old = s[right - k]
            window[old] -= 1
            if window[old] == 0:
                del window[old]
        if right >= k - 1 and window == need:
            ans.append(right - k + 1)
    return ans

For interviews, explain the tradeoff: full counter comparison is fine when the alphabet is fixed and small; a matches-based implementation is better when you need strict O(n) with a larger character set.

For minimum window substring, maintain how many required characters are currently satisfied:

  • need[c] = required count
  • have[c] = current count
  • formed = number of characters where have[c] >= need[c]
  • valid when formed == len(need)

This is a shortest-valid-window problem, not a longest-valid-window problem.

The "at most K" counting trick

Some LeetCode problems ask for the number of subarrays with exactly K distinct values. Counting exact windows directly is awkward. The clean trick:

exactly(K) = at_most(K) - at_most(K - 1)

The at_most helper counts all valid subarrays ending at each right.

from collections import defaultdict

def subarrays_with_at_most_k(nums, k):
    if k < 0:
        return 0
    left = 0
    count = defaultdict(int)
    ans = 0

    for right, x in enumerate(nums):
        count[x] += 1
        while len(count) > k:
            old = nums[left]
            count[old] -= 1
            if count[old] == 0:
                del count[old]
            left += 1
        ans += right - left + 1
    return ans

def subarrays_with_exactly_k(nums, k):
    return subarrays_with_at_most_k(nums, k) - subarrays_with_at_most_k(nums, k - 1)

Why right - left + 1? Once the window [left, right] is valid for at most K, every subarray ending at right and starting between left and right is also valid. That single line is the insight.

Replacement problems and stale max counts

The classic example is Longest Repeating Character Replacement: choose at most k characters to replace so the window becomes all one character.

Condition:

window_length - max_frequency <= k

Template:

from collections import defaultdict

def character_replacement(s, k):
    left = 0
    count = defaultdict(int)
    max_freq = 0
    best = 0

    for right, ch in enumerate(s):
        count[ch] += 1
        max_freq = max(max_freq, count[ch])

        while (right - left + 1) - max_freq > k:
            count[s[left]] -= 1
            left += 1

        best = max(best, right - left + 1)
    return best

A subtle point: max_freq can be stale after shrinking. That is okay for this longest-window problem because a stale high value may delay shrinking, but it will not cause a too-large answer to be accepted incorrectly; the best length is still found when a real max frequency supported it. If you are uncomfortable explaining that, recompute max_freq in the loop for a small fixed alphabet. Correctness clarity beats cleverness.

Monotonic deque: sliding window maximum

Some sliding-window problems need the best element inside each fixed window, not just a sum or count. Use a monotonic deque.

from collections import deque

def max_sliding_window(nums, k):
    dq = deque()  # stores indices, values decreasing
    ans = []

    for right, x in enumerate(nums):
        while dq and dq[0] <= right - k:
            dq.popleft()
        while dq and nums[dq[-1]] <= x:
            dq.pop()
        dq.append(right)
        if right >= k - 1:
            ans.append(nums[dq[0]])
    return ans

The deque stores candidates, not the whole window. Old indices leave from the front. Smaller values behind the new value are removed because they can never become maximum while the new value is in the window.

This is still a sliding-window pattern because each index enters and leaves the deque at most once.

When sliding window is the wrong tool

Part of mastering the pattern is knowing when not to use it. If the prompt says subsequence, graph path, arbitrary reorder, or non-contiguous selection, sliding window is usually the wrong frame. If the sum condition includes negative numbers, test the monotonic assumption before committing. A window that becomes valid at right may become invalid or more valid in surprising ways after removing a negative number.

Common alternatives:

  • Prefix sum + hashmap for subarray sum equals K with negative numbers.
  • Prefix sum + monotonic deque for shortest subarray at least K when negatives exist.
  • Dynamic programming for subsequences where elements can be skipped.
  • Sorting + two pointers for pair or triplet problems after order no longer represents original contiguity.

A useful interview sentence is: "I would use sliding window only if moving the left pointer predictably restores validity. If negatives or non-contiguous choices break that, I will switch to prefix sums or DP." That shows judgment, not just template recall.

Common traps

Trap: Using sliding window when negatives break monotonicity. For shortest sum at least target, basic sliding window requires positive numbers. With negatives, use prefix sums and different tools.

Trap: Not stating the invariant. Code with two pointers is not automatically sliding window. Say what makes the window valid or invalid.

Trap: Updating answer in the wrong place. For longest valid windows, update after shrinking invalid state. For shortest valid windows, update inside the while-valid shrink loop.

Trap: Confusing exact K and at most K. Exact K is often easier as at most K minus at most K minus one.

Trap: Deleting frequency keys too late. If a count reaches zero but remains in the map, len(count) becomes wrong.

Trap: Forgetting window length. In Python, the inclusive window length is right - left + 1.

Interview checklist

Before coding, answer these out loud:

  1. Is the segment contiguous?
  2. Is the window length fixed or variable?
  3. What state do I need: sum, count map, max frequency, deque, or matched requirements?
  4. What makes the window invalid or valid?
  5. Do I update best before or after shrinking?
  6. Does each pointer move only forward?
  7. Do constraints allow negative numbers that break the approach?
  8. What is the time and space complexity?

A concise closing explanation: "The right pointer expands the candidate window, the left pointer restores the invariant, and every element is added and removed at most once, so the scan is linear." If you can say that and your invariant is correct, sliding window problems become much less mysterious.