Sliding Window
Keep a running summary of a contiguous slice, and update it in O(1) as the slice moves.
▶ guided lesson⚡ 1-minute summary↻ 14 recall cards
before this: Two Pointers · Arrays & Strings · Big-O
01 · why it exists
Why this exists, and the weight it carries
since?
Fixed-size sliding window
Running totals over the last k values are as old as moving averages in statistics; as an array technique it is programming folklore, and we found no single inventor to credit.
its jobs today
- Monitoring dashboards: "average response time over the last 5 minutes" and 7-day moving averages are fixed windows over a time series — add the newest reading, subtract the oldest.
- Stream processing: Apache Flink and Kafka Streams ship tumbling and sliding windows as built-in aggregations.
- Text and DNA search: a rolling hash (Rabin–Karp) slides a window of k characters across the text; genomics tools scan k-mers the same way.
the weight it carries
Illustrative: one reading per second for a day is 86,400 readings, and a dashboard shows the 5-minute (k = 300) average at every second. Re-adding every window: 86,400 × 300 = 25,920,000 additions (about 2.6 crore). Sliding: one add + one subtract per second = 172,800 operations — 150× less.
without it
Every rolling average or "last k" check would re-scan its whole window: O(n·k) work, which turns into crores of operations on a single day of per-second data.
when · why · how
- when
- The size is given: "every k consecutive", "subarray of length k", "indices at most k apart", "an anagram of p" (size |p|).
- why
- Neighbouring windows share k − 1 elements, so each slide costs O(1) instead of O(k): O(n) total instead of O(n·k), with O(1) extra space for a sum (O(σ) for a count table).
- how
- Build the first k, then
for (int right = k; right < n; right++) sum += nums[right] - nums[right - k];— uselongwhen k × max value can pass 2·10⁹.
since?
Variable-size window (grow and shrink)
The grow-right, shrink-left loop is programming folklore, usually taught as a form of same-direction two pointers; we found no single inventor to credit.
its jobs today
- Rate limiting: "at most 100 requests in any 60 seconds" keeps the timestamps inside the window — new requests enter on the right, those older than 60 s leave on the left.
- Search snippets: the shortest stretch of a document that contains every query word is a covering window — Minimum Window Substring.
- Log and streak analysis: "the longest stretch with at most k errors" or "the longest streak with at most k missed days" is Max Consecutive Ones III in disguise.
the weight it carries
Illustrative: a log of n = 10⁵ characters, longest stretch with no repeated character. All substrings: n(n + 1) / 2 = 5,000,050,000 windows (about 5·10⁹). Grow-and-shrink: right moves 10⁵ times and left at most 10⁵ times — at most 2·10⁵ pointer moves.
without it
Every "longest / shortest substring such that…" question falls back to checking all n²/2 substrings — about 5·10⁹ for n = 10⁵, minutes instead of milliseconds.
when · why · how
- when
- You must find the longest or shortest contiguous stretch, and the rule is monotonic: if a window breaks it, every bigger window containing it breaks it too.
- why
- Each index enters once and leaves at most once: O(n) amortized instead of O(n²) substrings. "Exactly k" = atMost(k) − atMost(k − 1) reuses the same O(n) loop twice.
- how
for (right…) { add(right); while (invalid) remove(left++); best = Math.max(best, right - left + 1); }with anint[128]/int[26]or aHashMapas the window state.
since2006
Monotonic deque (max / min of every window)
Daniel Lemire (a well-known published version; we found no first inventor)
The idea — keep only the values that can still become the max — is widely taught with no name attached; we couldn't trace who first used it. In 2006 Daniel Lemire published a version of it as a streaming maximum–minimum filter in the Nordic Journal of Computing. The positions of candidate maxima and minima live in double-ended queues, and each new value costs at most 3 comparisons. The GNU Scientific Library's moving-window min/max accumulators use his algorithm.
the problem it solvedThe max or min of every window of a long stream — without re-scanning the window and without waiting for future values.
its jobs today
- Signal processing: running min/max filters over audio and sensor data — the GNU Scientific Library's moving-window accumulators are one ready-made implementation.
- Monitoring: "peak CPU in the last 60 seconds", reported every second over a long trace.
- Dynamic programming: "best of the last k states" (jump games with a limit k) drops from O(n·k) to O(n).
the weight it carries
Illustrative: 10 lakh (10⁶) readings with a window of k = 1,000. Re-scanning every window: about 10⁶ × 1,000 = 10⁹ comparisons. Deque: every index is pushed once and popped at most once — at most 2·10⁶ deque operations (Lemire's bound: at most 3 comparisons per element, so 3·10⁶).
without it
The window max would need a full re-scan per step (O(n·k)), or a heap / TreeMap at O(log k) per step plus clean-up of values that already left the window.
when · why · how
- when
- You need the max or min of every window (fixed or variable), or a DP that takes the best of the last k states.
- why
- Older values that are no bigger than a newer one can never be the max again, so they are dropped for good: O(n) total, O(k) space. A heap would cost O(n log k).
- how
Deque<Integer> dq = new ArrayDeque<>()of indices: pop the back whilenums[dq.peekLast()] <= nums[right], drop the front whendq.peekFirst() <= right - k, read the max asnums[dq.peekFirst()].
02 · plain english
The idea in 30 seconds
Many questions ask about the best contiguous subarray or substring. Checking every one is O(n²) or worse.
Instead, keep a window [left, right] and a small summary of it (a sum, a count table). When the window moves, update the summary: one element in, one element out.
right only moves forward and left only moves forward, so each element is added once and removed once: O(n).
picture this
Task Manager's CPU graph shows only the last 60 seconds. Every second, one new reading appears on the right and the oldest drops off the left — the whole minute is never redrawn. A sliding window updates its summary the same way: one in, one out.
03 · remember
What you must know
- Fixed window (size k is given): build the first k, then each slide adds
nums[right]and removesnums[right - k]. - Variable window (find the longest / shortest valid slice): grow with
right; while the window breaks the rule, shrink withleft. - The window state is whatever lets you answer "is this window valid?" in O(1): a running sum, a zero count, an
int[26]letter count, a HashMap. - Longest valid window → record the answer after the shrink loop. Shortest valid window → record it inside the shrink loop, before each shrink.
- Shrinking is only safe when the rule is monotonic: if a window is invalid, every bigger window containing it is invalid too (for shortest problems, the mirror: if a window is valid, every bigger one is valid). Negative numbers usually break this for sums.
- The nested
whiledoes not make it O(n²):leftmoves at most n times in total over the whole run (amortized O(1) per step). - Need the max or min of every window? Keep a monotonic deque of indices — the front is always the answer.
- Sliding window is same-direction two pointers where you care about everything between the pointers, not just the two ends.
04 · see it
Play with it in 3D
Press Space to play, ←/→ to step, drag to move, ? for all shortcuts. Try your own input.
Fixed window — one in, one out
The window keeps its size. Each slide is two operations — add the new number, drop the oldest — not k.
Variable window — grow, then shrink
`right` grows the window until the sum is big enough; `left` shrinks it while it stays valid.
Window state in a count table
The buckets show what is inside the window. A red bucket means a duplicate — shrink until it is gone.
Monotonic deque — the max of every window
Values in the deque only decrease. Smaller, older values are popped because they can never win again.
05 · cost
Time & space, explained
| Time | Extra space | why | |
|---|---|---|---|
| Brute force: every subarray, recompute from scratch | O(n²)–O(n³) | O(1) | n²/2 subarrays, and summing or checking each one costs up to O(n) more. |
| Fixed window of size k | O(n) | O(1) | Build the first window in k steps, then n − k slides of O(1) each. |
| Variable window (grow + shrink) | O(n) | O(1) | right moves n times and left moves at most n times in total — at most 2n steps. Amortized O(1) per step. |
Window + count array int[26] / int[128] | O(n) | O(σ) = O(1) | σ is the alphabet size. The array has a fixed size no matter how long the input is. |
| Window + HashMap counts | O(n) average | O(distinct) | HashMap updates are O(1) on average; the map holds one entry per distinct key in the window. |
| Window max/min with a monotonic deque | O(n) | O(k) | Each index is pushed once and popped at most once; the deque never holds more than k indices. |
| Time | why | |
|---|---|---|
Add nums[right] to a running sum / count | O(1) | One addition or one array increment. |
Remove nums[left] | O(1) | One subtraction or decrement — no shifting, the array is never modified. |
"Is this window an anagram?" via Arrays.equals(need, have) | O(26) = O(1) | Compares two fixed-size arrays. Fine, but a matches counter makes it truly one step. |
| Window maximum by rescanning | O(k) | Looks at every element in the window again — this is what makes the brute force O(n·k). |
| Window maximum from a monotonic deque | O(1) | The answer is always nums[deque.peekFirst()]. |
06 · java
Raw vs Collections Framework
Template 1 — fixed-size window
class FixedWindow { /** Largest sum of any k consecutive numbers (1 ≤ k ≤ nums.length). */ static int maxSumOfK(int[] nums, int k) { int sum = 0; for (int i = 0; i < k; i++) sum += nums[i]; // build the first window int best = sum; for (int right = k; right < nums.length; right++) { sum += nums[right] - nums[right - k]; // one in, one out best = Math.max(best, sum); } return best; }}- Two phases: build the first window (
i < k), then slide (rightfrom k to n − 1). - The element leaving is always
nums[right - k]— no separateleftvariable needed. - If sums can exceed ~2·10⁹ (e.g. 10⁵ values of 10⁵), use
long sum.
Template 2 — variable-size window
class VariableWindow { /** Longest subarray with sum ≤ limit (all numbers are non-negative). */ static int longestWithSumAtMost(int[] nums, int limit) { int left = 0, sum = 0, best = 0; for (int right = 0; right < nums.length; right++) { sum += nums[right]; // 1. grow: take nums[right] while (sum > limit) { // 2. shrink while the window breaks the rule sum -= nums[left++]; } best = Math.max(best, right - left + 1); // 3. window is valid: record it } return best; }}- Grow → shrink while invalid → record. Memorise this order; most variable-window problems are exactly this loop.
- Use
while, notif, to shrink: one new element can require several removals. - The window
[left, right]has lengthright - left + 1.
Window state: count array vs HashMap
class CountArray { /** How many length-p windows of s are anagrams of p? Lowercase letters only. */ static int countAnagramWindows(String s, String p) { if (p.length() > s.length()) return 0; int[] need = new int[26], have = new int[26]; for (char c : p.toCharArray()) need[c - 'a']++; int found = 0; for (int right = 0; right < s.length(); right++) { have[s.charAt(right) - 'a']++; // add the new char if (right >= p.length()) have[s.charAt(right - p.length()) - 'a']--; // drop the old one if (right >= p.length() - 1 && Arrays.equals(need, have)) found++; // 26 checks = O(1) } return found; }}class CountMap { /** Same idea with a HashMap: works for any characters, not just 'a'..'z'. */ static int countAnagramWindows(String s, String p) { if (p.length() > s.length()) return 0; Map<Character, Integer> need = new HashMap<>(), have = new HashMap<>(); for (char c : p.toCharArray()) need.merge(c, 1, Integer::sum); int found = 0; for (int right = 0; right < s.length(); right++) { have.merge(s.charAt(right), 1, Integer::sum); if (right >= p.length()) { char old = s.charAt(right - p.length()); if (have.merge(old, -1, Integer::sum) == 0) have.remove(old); // keep the map clean } if (right >= p.length() - 1 && have.equals(need)) found++; } return found; }}- Known small alphabet (lowercase letters, ASCII) →
int[26]orint[128]: faster, no boxing,Arrays.equalsworks. c - 'a'turns a lowercase letter into an index 0–25. For any ASCII char, indexint[128]with the char itself.- Unknown or huge key space (Unicode, integers, words) →
HashMapwithmerge(key, 1, Integer::sum). - When a count drops to 0,
removethe key — otherwisemap.equalsandmap.size()will lie.
Monotonic deque with ArrayDeque
class Solution { public int[] maxSlidingWindow(int[] nums, int k) { Deque<Integer> dq = new ArrayDeque<>(); // indices; values decrease front -> back int[] result = new int[nums.length - k + 1]; for (int right = 0; right < nums.length; right++) { if (!dq.isEmpty() && dq.peekFirst() <= right - k) dq.pollFirst(); // fell out of the window while (!dq.isEmpty() && nums[dq.peekLast()] <= nums[right]) dq.pollLast(); // can never be max again dq.offerLast(right); if (right >= k - 1) result[right - k + 1] = nums[dq.peekFirst()]; // front = max } return result; }}- Store indices, not values: you need the index to know when an element has left the window.
ArrayDequegives O(1) at both ends. Never use the legacyStackclass or aLinkedListhere.- Pop from the back while the back's value ≤ the new value; the front is then always the window max.
| call | does | cost |
|---|---|---|
s.charAt(i) | Read one character without copying the string | O(1) |
count[c - 'a']++ | Count a lowercase letter in an int[26] | O(1) |
Arrays.equals(a, b) | Compare two count arrays element by element | O(len) |
map.merge(key, 1, Integer::sum) | Increment a count, creating it at 1 if missing | O(1) avg |
map.getOrDefault(key, 0) | Read a count without null checks | O(1) avg |
map.remove(key) | Drop a key whose count reached 0 | O(1) avg |
set.add(x) | Adds x; returns false if x was already in the window | O(1) avg |
deque.offerLast(x) / pollLast() | Push / pop at the back of an ArrayDeque | O(1) amortized |
deque.peekFirst() / pollFirst() | Read / remove the front (the window max) | O(1) |
Window state over lowercase letters
→ int[26]
Tiny, fast, no boxing; compare with Arrays.equals.
Window state over any characters
→ int[128] (ASCII) or HashMap<Character, Integer>
Array if the range is known and small, map otherwise.
Window state over integers / words
→ HashMap<K, Integer>
Key space is too big for an array.
"Is this value already in the last k?"
→ HashSet<Integer> of the last k values
add returns false on a repeat; remove the value that slides out.
Max or min of every window
→ ArrayDeque<Integer> of indices (monotonic)
O(1) amortized per step; a heap would be O(log n).
Just a sum or a count of something
→ One int / long variable
No structure needed at all.
07 · recognise
Spot the pattern
"Contiguous subarray" or "substring" + longest / shortest / maximum / minimum
Variable sliding window
"Of size k", "every k consecutive", "average of each window"
Fixed sliding window
"At most k" distinct / zeros / replacements
Variable window; shrink while the count exceeds k
"Contains all characters of", "anagram", "permutation of" inside a longer string
Window + count array (fixed size for permutations, variable for covering)
Equal values "at most k indices apart"
Fixed window of the last k values in a HashSet
"Concatenation of all words" (every word the same length)
Window over words instead of letters — one run per starting offset
"Maximum / minimum of every window"
Monotonic deque of indices
Subarray sum equals k with negative numbers
Not a window — use prefix sums + HashMap (chapter on hashing & prefix sums)
08 · traps & tricks
Mistakes to avoid, tricks to use
traps
- Using
ifinstead ofwhileto shrink — one new element may need several removals. - Recording the answer in the wrong place: longest → after the shrink loop; shortest → inside it.
- Applying a sum window to arrays with negative numbers: removing an element can make the sum go up, so shrinking is no longer safe.
- Off-by-one in window length: it is
right - left + 1, notright - left. - Fixed window: forgetting that the first full window ends at
right = k - 1, so results start there. - Storing values in the deque for window max — you then can't tell when the front has left the window.
- Not removing zero-count keys from a HashMap, so
map.size()still counts characters that left the window.
tricks
- Say the template out loud: "expand right, shrink left while invalid, record". Then fill in what valid means.
- Before coding, define the window state in one sentence: "
zeros= number of 0s in the window." - Ask yourself: "If this window is invalid, is every bigger window invalid too?" If yes, sliding window works.
- Dry-run on a tiny input (5–6 elements) and write
left,rightand the state after each step. - Edge cases: empty input, k = 1, k = n, every element valid, no valid window at all (return 0 or "").
- "At most k" problems can also answer "exactly k":
exactly(k) = atMost(k) − atMost(k − 1).
09 · why & how
Questions you might be too shy to ask
How do I decide between a fixed and a variable window?
If the problem gives you the window size ("subarrays of length k"), it is fixed. If it asks you to find the best length ("longest", "shortest", "minimum window"), it is variable.
Why is the window length right - left + 1 and not right - left?
Both ends are inside the window.
Example: left = 2, right = 4 covers indices 2, 3, 4 — that is 3 elements, and 4 - 2 = 2 misses one.
Quick check: when left == right the window holds one element, so the formula must give 1.
In the fixed window, why does the slide loop start at right = k, and why does nums[right - k] leave?
The first window is indices 0..k-1, built before the loop. So the first new element is at index k.
The window after adding right must be right-k+1..right. The one index that just fell off is right - k.
Dry-run with k = 3: when right = 3 enters, index 3 - 3 = 0 leaves. Simple hai.
There is a while loop inside a for loop. Why isn't it O(n²)?
Count the moves of left, not the loop nesting. left only moves forward and never passes right, so over the whole run it moves at most n times. Total work: n moves of right + at most n moves of left = O(n).
When I move left forward, how do I know I didn't skip the best window?
In a longest problem you shrink only when [left..right] is invalid.
Any window that starts at the old left and ends at right or later contains that invalid window, so it is invalid too (the monotonic rule).
So the old left can never start a better answer again — throwing it away loses nothing.
This is exactly why the rule must be monotonic before you trust the window.
Can left ever go past right? Is that a bug?
It can, by one, and that is fine. In Minimum Size Subarray Sum with nums = [5], target = 4: the shrink loop records length 1, subtracts 5 and moves left to 1 while right is 0.
That just means the window is empty (sum 0). The next right grows it again.
You only record while the window is valid, so an empty window is never recorded.
When does sliding window not work?
When shrinking can't be justified. Example: "subarray sum equals k" with negative numbers. Dropping a negative number makes the sum bigger, so "sum too big → shrink" is wrong. Use prefix sums + a HashMap there.
Where exactly do I update the answer?
For the longest valid window, update after the shrink loop (the window is valid again). For the shortest valid window, update inside the shrink loop, before removing nums[left] (each iteration is a valid window).
Should I use int[26] or a HashMap for the counts?
If the problem says lowercase (or uppercase) English letters, use int[26] — it is faster and simpler. Use int[128] for general ASCII. Use a HashMap only when keys are unbounded (integers, words, Unicode).
The problem asks me to count subarrays, not find the longest. How does a window count them?
After the shrink loop, [left..right] is valid, and so is every shorter window that ends at right: [left+1..right], …, [right..right].
That is right - left + 1 windows, so add that to the count at every step.
For "exactly k", count atMost(k) - atMost(k - 1): a window with exactly k distinct values is counted in the first run but not in the second.
In Longest Repeating Character Replacement, why is it OK that maxFreq never goes down?
The answer only grows when a window has a higher maxFreq than before. A stale (too big) maxFreq can only keep the window the same size, never let an invalid window become the new best. So we skip the expensive recount.
In Minimum Window Substring, why does need[c] go negative, and why does missing change only sometimes?
need[c] = how many more copies of c the window still needs. Negative means spare copies.
Example: t = "ABC" and the window has two B's → need['B'] = -1, one spare.
Dropping a spare B takes need['B'] back to 0 — still covered, so missing stays the same.
missing counts the copies of t still not covered. It drops by one when an arriving c was still needed (need[c] was above 0), and rises by one when a leaving c takes need[c] from 0 up to 1.
Is sliding window the same as two pointers?
It is a kind of same-direction two pointers. The difference is focus: sliding window keeps a summary of everything between left and right; basic two pointers usually only looks at the two elements being pointed at.
I understand the solutions when I read them, but I freeze on a new problem. What now?
That is normal — reading and writing are different skills. Before any code, answer three questions on paper: what is the window state? When is the window invalid? Am I finding the longest or the shortest? Then write the template from memory and fill in only those three blanks. Practise in this order: 643 → 3 → 1004 → 209 → 76. Each one reuses the last one's loop.
10 · practice
Interview problems
Every problem has brute → better → optimal solutions in Java, with complexity and a dry run.
- #643Maximum Average Subarray IEasyMetaBloombergGoogleAmazonMicrosoft
- #3Longest Substring Without Repeating Charactersmust-doMediumWalmartInfosysAdobeNetflixByteDance / TikTok+1
- #424Longest Repeating Character Replacementmust-doMediumBloombergAmazonGoogleMicrosoftByteDance / TikTok+1
- #567
- #438
- #76
- #209
- #1004
- #219Contains Duplicate IIEasyNetflixAirbnbAmazonGoogleMicrosoft
- #30Substring with Concatenation of All WordsHardSamsungAmazonGoogleMicrosoft
Also practise this here
Each problem has one home page in the course; these use this chapter's idea too. The note says what to look for.
- #239Sliding Window MaximumHardin Queues
A fixed window whose max can't be updated by add/subtract — a monotonic deque keeps it in O(1) amortized.
- #933Number of Recent CallsEasyin Queues
A window over time instead of indices: keep only the pings from the last 3000 ms, dropping old ones from the front.
- #862Shortest Subarray with Sum at Least KHardin Queues
LC 209 with negative numbers: the plain window breaks, prefix sums + a monotonic deque fix it.
- #560Subarray Sum Equals KMediumin Hashing
Exact sums with negatives are not a window problem — prefix sums + a HashMap count them in O(n).
11 · revise
The whole chapter on one screen
9 cards, about a minute. Read them the night before an interview, or right after the chapter to lock it in.
- what it is
A window
[left, right]over a contiguous slice plus a tiny summary of it. Each move adds one element and removes one, so the whole scan is O(n). - use it when
"Contiguous subarray / substring" + longest, shortest, max or min; "of size k"; "at most k"; an anagram or permutation inside a longer string.
- template
Fixed: build the first k, then
sum += nums[r] - nums[r - k];. The element leaving is alwaysnums[r - k]. - template
Variable:
for (r…) { add(r); while (invalid) remove(l++); best = max(best, r - l + 1); }— grow, shrink while invalid, record. - cost
O(n):
rightmoves n times,leftat most n times. Space: O(1) for a sum, O(σ) forint[26]/int[128], O(k) for a deque. - trap
Negative numbers break sum windows: removing one can raise the sum. Use prefix sums + a
HashMapinstead. - trap
Longest → record after the shrink loop. Shortest → record inside it, before removing
nums[left]. - trap
Shrink with
while, notif: one new element can force several removals. - pro tip
"Exactly k" =
atMost(k) - atMost(k - 1). Max or min of every window → a monotonicArrayDequeof indices.
12 · recall
Flip, remember, repeat
There's a while inside a for. Why is the variable window still O(n)?
Question 1: There's a while inside a for. Why is the variable window still O(n)?
13 · check yourself
Quick quiz
Fixed window, k = 3, nums = [2, 1, 5, 1, 3]. The first window sum is 8. What is the sum after one slide?
Why is the variable window O(n) even though it has a while loop inside a for loop?
"Count subarrays with sum exactly k" on an array with negative numbers. Best tool?
Window-max deque holds values [9, 4, 2] (front → back). The new value is 5. What does the deque hold after the push?
LC 424, k = 1. The window is "AABAB". Is it valid?
You want the **shortest** valid window. Where do you update the answer?
Window state over lowercase letters only. Which is the best choice?
LC 219 with k = 2, nums = [1, 2, 3, 1]. Just before index 3 is checked, what does the HashSet window hold — and what is returned?
LC 30 with words of length 3. How many separate word-by-word windows does the optimal solution run?
14 · learn more
A second teacher, if you want one
- ▶ playlistSliding Window Algorithm | Face to Face Interviews ↗Aditya Verma · HindiTeaches you to recognise a window problem, then gives one format for fixed and variable windows. Bas, the same loop every time.
- ▶ playlistTwo Pointer and Sliding Window Playlist | Language Independent Course ↗Striver (take U forward) · EnglishStarts with the templates, then works through eleven classic problems up to Minimum Window Substring, all built from the same expand-and-shrink loop.
- ▶ playlistSliding Window ↗NeetCode · EnglishShort, visual walkthroughs of the most-asked window problems. Use it as a second explanation when the first one doesn't click.