
LeetCode (4): Patterns — Sliding Window Technique
Master fixed-size and variable-size sliding window patterns. Solve Maximum Sum Subarray, Longest Substring Without Repeating Characters, Minimum Window Substring, and Permutation in String.

If you have ever caught yourself writing a double for loop to inspect every contiguous subarray, sliding window is probably the optimisation you are missing. It turns an $O(nk)$
or $O(n^2)$
scan into a single linear pass by reusing the work it has already done. This article walks through the technique from first principles, then drills four canonical LeetCode problems plus a monotonic-deque variant.
The Idea in One Picture#
A sliding window is a contiguous range [left, right] over an array or string. Instead of recomputing everything when the range moves, we add the element entering on the right and remove the element leaving on the left. Each element is touched at most twice, so the total cost is $O(n)$
.
There are two flavours:
- Fixed-size window — width is a constant
kand both pointers advance together. - Variable-size window —
rightexpands to grow the window,leftcontracts to repair it when an invariant breaks.
A picture is worth a thousand words. Below, the green cells are the live window and the right column shows the incremental sum update.

The orange cell is entering the window; the pink one is leaving. Notice the right column: each step is one subtraction and one addition, never a fresh sum() call. That is the entire trick.
Fixed-Size Window — Maximum Sum Subarray of Size K#
Problem. Given an array arr and an integer k, find the maximum sum of any contiguous subarray of length k.
Naive approach. Sum every length-k slice — $O(nk)$
.
Sliding window. Compute the first window’s sum, then slide one position at a time, subtracting the element that leaves and adding the element that enters.
| |
Java
| |
Complexity. Time $O(n)$ — one addition and one subtraction per slide. Space $O(1)$ .
Why it works. The window’s sum is a function of its multiset of elements. When the window shifts by one, the multiset changes by exactly one removal and one insertion, so we only need to apply that delta. This incremental update generalises to running counts, frequency maps, and even monotonic deques (Section 6 ).
Variable-Size Window — Two Postures#
When the window size is not given, we use two pointers and let the problem dictate the policy:
- Find the longest valid window: keep expanding
right; whenever the window goes invalid, contractleftuntil it is valid again, then record the length. - Find the shortest valid window: keep expanding
rightuntil the window becomes valid; then contractleftas far as possible while staying valid, recording the length on each shrink.
The picture below traces the longest-substring-without-repeating-characters algorithm on "abcabcbb". Green = valid (expanding), pink = duplicate detected, purple = contracting to repair.

And here is what the window length looks like over time. Green bands are expansion phases, purple bands are contraction phases.

The crucial invariant: left only ever moves right. So even though there is a while inside a for, the inner loop’s total work across the whole run is bounded by n. That is the amortised-$O(n)$
argument.
LeetCode 3 — Longest Substring Without Repeating Characters#

Problem. Given a string s, return the length of the longest substring with all distinct characters.
Pattern. Variable window, find longest. The invariant is “all characters in the window are unique”. When s[right] is already in the window, slide left past its previous occurrence.
| |
Java
| |
Complexity. Time $O(n)$ — each character is inserted into and removed from the map at most once. Space $O(\min(n, |\Sigma|))$ where $|\Sigma|$ is the alphabet size.
Two implementation styles. The version above uses jump-left — left leaps directly past the previous occurrence. The textbook alternative is step-left: hold a frequency map and while count[s[right]] > 1: drop(s[left]); left += 1. Both are $O(n)$
; jump-left is one line shorter, step-left generalises better to “at most k” variants.
LeetCode 76 — Minimum Window Substring#
Problem. Given s and t, return the shortest substring of s that contains every character of t (with multiplicities). Return "" if no such window exists.
Pattern. Variable window, find shortest. The invariant is “the window contains all required characters at the required counts”. We expand until the invariant holds, then aggressively contract.
The cleanest way to track validity is a valid counter — the number of distinct required characters whose count in the window has reached the required threshold. The window is valid iff valid == len(need). This avoids comparing two hash maps on every step.
| |
Java
| |
Complexity. Time $O(|s| + |t|)$ — both pointers advance monotonically. Space $O(|\Sigma|)$ .
The subtle line is if have[drop] == need[drop]: valid -= 1 — we decrement valid before the have count actually falls below the threshold, because we’re about to make it fall. Get the order wrong and the counter desyncs.
LeetCode 567 — Permutation in String#
Problem. Return True iff some substring of s2 is a permutation of s1.
Pattern. Fixed-size window of width len(s1). Two strings are permutations of each other iff their character-frequency vectors are equal. So we maintain a length-26 frequency vector for the current window in s2 and compare to the frequency vector of s1.
| |
Java
| |
Complexity. Time $O(m)$
— every slide is a constant amount of work because we maintain matches incrementally instead of rescanning all 26 buckets. Space $O(1)$
.
The same skeleton solves LC 438 (Find All Anagrams) verbatim — just collect the start indices instead of returning early.
Variant: Sliding Window Maximum (Monotonic Deque)#
When the window asks for the min or max of its elements, the simple “add / remove” trick is not enough — removing the current max means we need to know the next-best element instantly. The standard tool is a monotonic deque that stores values (or indices) in strictly decreasing order. The front of the deque is always the current window max.

| |
Each index is pushed and popped at most once, so total work is $O(n)$ — the same linear bound as the basic patterns.
When to Reach for Sliding Window#
The decision tree below summarises which flavour fits which problem shape:

Reach for sliding window when all of the following are true:
- The answer is over a contiguous range (subarray / substring).
- The window’s value can be maintained incrementally as the boundaries move (running sum, frequency map, monotonic deque, …).
- The objective rewards either a specific size (fixed
k) or a boundary condition (longest valid / shortest valid).
If any of those fails — non-contiguous picks, requiring a global view, or needing to undo arbitrary insertions — try prefix sums, two pointers on sorted data, dynamic programming, or a heap instead.
Templates Worth Memorising#
Fixed-size window
| |
Variable window — longest valid
| |
Variable window — shortest valid
| |
Common Pitfalls#
- Off-by-one in length. Window length is
right - left + 1, notright - left. - Updating
validafter the count change. Incrementvalidexactly whenhave[c]first equalsneed[c]; decrement before it falls below. The order matters. - Using
list.pop(0)for fixed windows. That is $O(k)$ . Usecollections.deque(or simple index arithmetic) for $O(1)$ . - Recomputing the window from scratch. The whole point is incremental update — if your inner loop scans the window, you have lost the asymptotic improvement.
- Negative numbers in “sum ≥ target”. Contracting may not reduce the sum, so the “shortest valid” template doesn’t apply directly. Reach for prefix sums + monotonic deque, or a different framing.
Practice Set#
| Difficulty | Problem | Pattern |
|---|---|---|
| Easy | LC 643 Maximum Average Subarray I | fixed |
| Easy | LC 1456 Max Vowels in a Substring of Given Length | fixed |
| Medium | LC 3 Longest Substring Without Repeating Characters | longest |
| Medium | LC 159 / 340 Longest Substring with At Most K Distinct | longest |
| Medium | LC 209 Minimum Size Subarray Sum | shortest |
| Medium | LC 424 Longest Repeating Character Replacement | longest |
| Medium | LC 567 Permutation in String | fixed |
| Medium | LC 438 Find All Anagrams in a String | fixed |
| Medium | LC 713 Subarray Product Less Than K | longest |
| Medium | LC 1004 Max Consecutive Ones III | longest |
| Hard | LC 76 Minimum Window Substring | shortest |
| Hard | LC 239 Sliding Window Maximum | monotonic deque |
| Hard | LC 30 Substring with Concatenation of All Words | fixed + hashing |
Where Sliding Window Shows Up Outside Interviews#
The pattern lives well beyond LeetCode. Three places I’ve actually shipped it:
- Rate limiting. A token-bucket and a sliding-window counter are the two standard answers. The “1000 requests per minute” implementation is literally a deque of timestamps, evicting from the left.
redis-celland most API gateways do exactly this. - Streaming aggregations. Any time series dashboard showing “errors per minute over the last hour” is a sliding window over an event stream. Kafka Streams’
TimeWindowand Flink’s tumbling/sliding windows are the same idea wrapped in a framework. - Network congestion control. TCP’s
cwnd(congestion window) is a sliding window over in-flight bytes. The receiver’s advertised window controls how far the sender’s left edge can advance.
The interview problem is the toy version. The production version is the same algorithm with timestamps instead of array indices and TTL eviction instead of pointer increments.
A Cleaner Python Idiom#
C-style while right < n loops work, but a single for right, x in enumerate(arr) reads better and removes one source of off-by-one bugs. Compare:
| |
The second version eliminates right += 1 (the most-forgotten line in interview rooms) and avoids re-indexing arr[right] inside the loop. Same algorithm, fewer places to break it.
For frequency-map windows, prefer collections.Counter over a hand-rolled dict — the += and -= semantics are well-tested:
| |
The del after decrement is what keeps len(cnt) honest — without it your “distinct count” silently includes zero-count keys.
Edge-Case Test Battery#
Before you submit, run your solution mentally against this list. Most “wrong answer” verdicts on sliding-window problems are one of these:
| Case | Why it breaks |
|---|---|
| Empty string / array | Off-by-one on the initial best = 0 vs best = float('inf') |
k = 0 for “at most K distinct” | The valid window is empty, but loops that always increment right first will record length 1 |
| Single element | Whether you compare > or >= against best shows up here |
| All identical elements | The “longest” template usually shines; the “shortest” template can collapse to length 1 immediately and never grow |
target larger than sum(arr) | “Min length subarray sum >= target” should return 0, not float('inf') |
| Negative numbers | Most sliding-window invariants assume monotonic growth on add; negatives break it. Switch to prefix sums + monotonic deque |
| Window larger than array | len(arr) < k for fixed-size templates needs an explicit guard |
Two minutes of mental dry-run on these saves the fifteen minutes of staring at “Wrong Answer on test case 87”.
Summary#
Sliding window is fundamentally an amortisation argument: instead of recomputing each window’s value from scratch, you maintain a tiny piece of state and update it as the window moves by one position. Two pointers, both monotonically increasing, give you an $O(n)$ algorithm where the brute force is $O(n^2)$ .
The mental checklist when you see a contiguous-range problem:
- Is the window size fixed or variable?
- If variable, am I after the longest or the shortest valid window?
- What state do I maintain — a sum, a frequency map, a monotonic deque?
- What is the invariant that determines validity, and which pointer’s move can break it?
Internalise those four questions and most sliding window problems collapse to filling in a template.
LeetCode Patterns 10 parts
- 01 LeetCode (1): Patterns — Hash Tables
- 02 LeetCode (2): Patterns — Two Pointers
- 03 LeetCode (3): Patterns — Linked List Operations
- 04 LeetCode (4): Patterns — Sliding Window Technique you are here
- 05 LeetCode (5): Patterns — Binary Search
- 06 LeetCode (6): Patterns — Binary Tree Traversal and Construction
- 07 LeetCode (7): Patterns — Dynamic Programming Basics
- 08 LeetCode (8): Patterns — Backtracking Algorithms
- 09 LeetCode (9): Patterns — Greedy Algorithms
- 10 LeetCode (10): Patterns — Stack and Queue