Community Article

Sliding Window Technique

What sliding window actually optimises (state reuse, not just same-direction pointers), the fixed vs variable templates, four canonical problems, and when the technique is the wrong tool.

Sliding Window Technique

What sliding window actually optimises (state reuse, not just same-direction pointers), the fixed vs variable templates, four canonical problems, and when the technique is the wrong tool.

sliding-window
two-pointers
array-manipulation-patterns
subarray-substring
interview-prep
sophiesharma

By @sophiesharma

May 16, 2026

·

Updated May 18, 2026

827 views

9

Rate

A senior engineer on my team once shipped a function that scanned a stream of click events for the longest run with no more than three distinct user-agents. They wrote it as a sliding window because the problem said "longest run with at most K". The window expanded on the right and shrunk on the left when the distinct count exceeded three. Tests passed. Production fell over.

The bug was that they recomputed the distinct-count from scratch on every shrink step, scanning the entire current window. The shape looked like sliding window. The work per step was O(window_size), not O(1). The whole thing was O(n^2) in disguise. They knew two pointers cold, recognised "two indices, both moving right", and reached for the technique. The technique was the right shape. The state reuse was missing.

That story is the argument I want to make. Sliding window is not "same-direction two pointers with extra steps". The defining feature is that the answer for the window [i, j+1] can be derived in O(1) from the answer for [i, j] plus a constant-cost adjustment. When that reuse is impossible, you do not have a sliding-window problem; you have a nested loop with prettier variable names.

The reuse property is the whole technique

The window is just two indices, left and right, both walking the array left-to-right. What makes it sliding-window rather than two-pointers is the running state attached to the window: a sum, a count, a frequency map, a max, whatever the problem needs. Each time right advances, the state absorbs one new element in O(1). Each time left advances, the state evicts one old element in O(1). The window never recomputes from scratch.

Total runtime is O(n) because each index crosses the array at most once, and each step does constant work. Drop the constant-time eviction and the whole bound collapses to O(n^2).

The mental check I run before writing any code: "if the window currently spans [i, j] and I move right to j+1, can I update the running state in O(1)? If I move left to i+1, can I evict in O(1)?" If the answer to either is no, I am not actually doing sliding window and I should stop pretending.

Fixed-size windows: when the size is in the prompt

The easy variant. The problem hands you the window size k and asks something about every contiguous k-element range. Maximum sum of k consecutive elements, average, count of even numbers, whatever.

The template is two phases: prime the window, then slide it.

def max_sum_of_k(arr, k):
    if len(arr) < k:
        return None
    window_sum = sum(arr[:k])        # prime
    best = window_sum
    for i in range(k, len(arr)):
        window_sum += arr[i] - arr[i - k]   # add new, evict old, O(1)
        best = max(best, window_sum)
    return best

The whole technique lives in that one line: window_sum += arr[i] - arr[i - k]. One add, one subtract, constant time, no rescan. If the running state is more complex than a sum (a frequency map, say), the eviction step decrements the outgoing element's count and deletes the key when it hits zero. Still O(1) amortised.

Variable-size windows: shrink when the predicate breaks

This is where most of the interview problems live, and where most of the bugs do too. The window is no longer fixed-width; it grows on the right and shrinks on the left based on a predicate.

The template I use:

def longest_with_at_most_k_distinct(s, k):
    counts = {}
    left = 0
    best = 0
    for right in range(len(s)):
        counts[s[right]] = counts.get(s[right], 0) + 1
        while len(counts) > k:                    # predicate broken
            counts[s[left]] -= 1
            if counts[s[left]] == 0:
                del counts[s[left]]
            left += 1
        best = max(best, right - left + 1)
    return best

Three things make this work. The outer for advances right exactly n times. The inner while advances left at most n times across the whole function (not per outer iteration). The predicate check and both updates are O(1). Total: O(n).

The bug from the opening anecdote was making the inner step O(window_size) by rescanning. The discipline is: the only allowed work inside the shrink loop is O(1) state maintenance plus the left += 1. If you find yourself iterating over the window, the technique is wrong for the problem, or the state shape is wrong for the technique.

Four problems and the shape they share

These four come up often enough that I keep the templates in muscle memory.

ProblemWindow kindRunning statePredicate / answer
Maximum sum of k consecutive elementsFixedrunning sumtrack max(best, sum) after each slide
Longest substring with at most k distinct charsVariablechar-frequency map + distinct countshrink while distinct > k
Smallest subarray with sum >= targetVariablerunning sumshrink while sum >= target, track min(best, length)
Longest substring without repeating charsVariablechar-last-seen map (or set)when seeing a repeat inside window, jump left past the previous occurrence

The shape underneath all four is identical: a window of contiguous indices, a constant-time absorb on the right, a constant-time evict on the left, and a predicate or extremum tracked across the slide. Switching from one problem to another is a matter of swapping the state and the predicate; the loop scaffold does not change.

The traps that make windows lie

Three pitfalls eat junior engineers (and, see opening, occasional senior ones).

First, non-amortised eviction. Recomputing distinct count, max, or any reduction across the whole window per shrink step. Looks fine on small inputs, blows up at scale. If your state cannot be updated in O(1) per step, sliding window is the wrong technique. Use a different structure (a balanced BST or a deque for sliding-window maximum, for example) or accept that the problem is not linear-time.

Second, off-by-one on window length. The length of the window [left, right] inclusive is right - left + 1, not right - left. Half the bugs in sliding-window code I review come from this single mistake. Write it down at the top of the function and refer to it.

Third, wrong predicate position. With variable windows, the predicate check goes inside a while, not an if, because a single right step can violate the predicate by more than one unit (think: a new character that pushes distinct-count well past k because the current window is already full). if would shrink only one slot; while shrinks until the predicate holds again.

When the window is the wrong shape

Sliding window earns its place on a small set of problems. It is the wrong tool more often than the social-media coverage suggests. If the answer requires looking at non-contiguous indices, stop, this is not your technique, you want subset-sum or DP. If the running state cannot be updated in constant time per step (sliding-window median, say, where every shift requires a sorted insertion), the linear-time bound is a fiction; either reach for a deque-based or two-heap variant or accept a higher complexity. If the problem asks about all subarrays rather than the optimum over them, no window will help, the answer space is O(n^2) by definition. The recognition signal I rely on is the reuse property from the first section. When I cannot describe, in one sentence, how absorbing one element and evicting one element keeps the answer correct, the problem is not sliding window. Knowing where the technique fails is what stops it from being the hammer that paints every problem the wrong colour.

Back to Articles