Skip to content

Algorithmic Patterns

Two pointers and sliding window

Scanning from both ends and maintaining a window

The brute-force way to find the best contiguous stretch of an array is to try every start, and from each start re-add every element of every window - work, most of it repeated. The trick is embarrassingly simple: keep a window and slide it. When it moves one step right, you already know almost everything about the new window - only one element left and one joined. Update the answer in instead of re-reading. Do that across the array and the whole scan is .

The idea in one line

Never re-read what you already read. A window from index to holds a summary (a sum, a count, a character map). Move a pointer by one, and patch the summary for the single element that entered and the single one that left - don't rebuild it. Two pointers that only ever move forward together touch each index a constant number of times, so the total work is linear.

Four windows, one trick

"Two pointers" is a family. Every member keeps two indices and advances them by a rule instead of nesting loops. Four shapes cover almost everything you'll meet:

  • Opposite endsPointers start at both ends of a sorted array and walk inward - the pattern behind two-sum, container-with-most-water, and palindrome checks.
  • Fast / slowTwo pointers over the same sequence at different speeds - cycle detection (Floyd's tortoise-and-hare) and finding a list's middle in one pass.
  • Fixed windowBoth pointers stay a constant distance apart and slide together - max sum of consecutive items, moving averages. This is the demo below.
  • Variable windowThe right pointer expands greedily; the left pointer shrinks to restore an invariant - longest substring without repeats, smallest window covering a target.

Slide the window yourself

Below is a fixed-size window of width over an array. Your goal: find the window with the largest sum. Drag the highlighted window left or right, or use the step buttons - and watch the two counters. Each single slide costs the sliding-window method just two operations (drop the element leaving on the left, add the one entering on the right), while a brute-force scan re-adds all elements from scratch every time. The green underline marks the best window found so far. Press Auto-scan to sweep the whole array and read off the final cost gap.

Sliding-window arena drag the window - step - auto-scan
04L
12
27R
31
49
53
66
78
82
95
101
114
window sum 13best so far 13 (indices 0-2)
3sliding-window ops (+ each edge)
3brute force (re-sum every window)

First window [0...2] costs 3 additions to sum - identical for both methods. Now slide it and watch the cost diverge.

The whole lesson is in that counter gap

Both methods pay the same additions to build the first window - there's no shortcut for that one. But from then on, every step the brute-force scan throws away its running total and re-adds all elements, while the window just does one subtraction and one addition. Set and Auto-scan: the window finishes in roughly operations; brute force pays about . The bigger the window, the wider the gap - yet the window's cost never depends on at all.

Why it's O(n), in symbols

Let be the sum of the window starting at index . The next window shares all but two elements, so

That's one recurrence, per step. Sweeping every window costs (the first sum) plus (two ops per slide) . Brute force instead recomputes each of the windows from scratch: work. The same -patch idea generalizes to prefix sums - precompute once in , then any window sum is in constant time, no sliding required.

In code

The fixed window is a one-line update inside a single loop. The variable window looks like a nested loop but isn't - each pointer marches forward at most times total, so the whole thing is still linear.

# Fixed window: max sum of any k consecutive items - O(n)
def max_window_sum(a, k):
    s = sum(a[:k])                 # first window: k additions, unavoidable
    best = s
    for r in range(k, len(a)):
        s += a[r] - a[r - k]       # add entering, drop leaving: O(1)
        best = max(best, s)
    return best

# Variable window: longest substring with no repeats - O(n)
def longest_unique(s):
    seen = {}                      # char -> last index it appeared
    left = best = 0
    for right, ch in enumerate(s):
        if ch in seen and seen[ch] >= left:
            left = seen[ch] + 1    # shrink: jump left past the repeat
        seen[ch] = right
        best = max(best, right - left + 1)
    return best

When the pattern applies - and when it lies

Sliding window works only for contiguous subarrays or substrings, and only when the window's answer moves monotonically as a pointer advances. Two traps: (1) it does not apply to problems that pick a scattered subset - those aren't a window. (2) The "grow-then-shrink" variable window assumes that extending the window can only worsen a constraint you then shrink to fix; with negative numbers, a longer window can have a smaller sum, so the shrink rule breaks - reach for prefix sums or a different structure instead.

Check yourself

When a fixed window slides one step right, how should the running sum update?

What kind of problem is the sliding-window pattern actually for?

What invariant makes a variable-size window run in O(n) despite its nested-looking loop?

Recall the loop invariant. Try to state it, then check.

Lock it in

  • The whole trick is to never re-read what you already read: patch the window summary for the one element that entered and the one that left, per slide.
  • Two pointers that only move forward touch each index a constant number of times, turning an scan into .
  • One trick, four shapes: opposite ends, fast/slow, fixed window, and variable (grow-then-shrink) window.
  • It applies only to contiguous subarrays or substrings whose answer moves monotonically; negative numbers break the shrink rule, so reach for prefix sums instead.

Primary source

Work the full progression of these problems on the NeetCode Roadmap - its Two Pointers and Sliding Window tracks drill exactly the four shapes above in order of difficulty. For the gentlest first exposure, with the friendliest diagrams, see Grokking Algorithms.

Ask your teacher

Want to see the opposite-ends variant (two-sum on a sorted array) or Floyd's fast/slow cycle trick stepped out the same way? Ask and I'll build it. A tie-in worth planting now, spelled out in sliding window to local attention in the LLM course: sliding-window attention in modern LLMs - Longformer and Mistral - is this exact idea. Full self-attention lets every token attend to all tokens, which is and explodes on long context. Restrict each token to a fixed window of neighbours and the cost drops to - the same "constant-width window sliding along a sequence" you just dragged, applied to attention instead of a running sum.