Why Sliding Window Exists — The Brute Force Problem
Before you learn the pattern, you need to understand the problem it solves. Once you see the inefficiency it eliminates, the technique becomes obvious.
The Train Window Analogy
Imagine you are on a train with a small window. As the train moves forward, you see new scenery entering from the right side of the window and old scenery leaving from the left. You never need to look backward — you just note what appeared and what disappeared.
Now imagine doing it the brute force way: at every position, you stop the train, get out, walk back to the beginning of the window, and re-examine everything inside it from scratch. That is what nested loops do. It is absurd once you see it.
Sliding window = let the train move and update incrementally. Instead of re-examining the entire window from scratch at each position, you process only the element that entered and the element that left. This transforms O(n × k) into O(n).
Brute Force vs. Sliding Window — Side by Side
Consider the simplest sliding window problem: “Find the maximum sum of any contiguous subarray of size k.” Here is how a beginner solves it vs. how you should solve it.
Brute Force — O(n × k)
For each starting index i from 0 to n-k, sum elements i through i+k-1. That is k additions for each of the n-k+1 positions. Total: O(n × k).
1def max_sum_brute(nums, k):2 """O(n*k) — re-sums k elements for EVERY position"""3 max_sum = float('-inf')4 for i in range(len(nums) - k + 1):5 # This inner loop is the problem:6 # we re-sum k elements from scratch7 current_sum = 08 for j in range(i, i + k):9 current_sum += nums[j]10 max_sum = max(max_sum, current_sum)11 return max_sum12# For n=1,000,000 and k=1000:13# ~1 BILLION operations. Too slow.Sliding Window — O(n)
Compute the first window sum once. Then slide: add the new element, subtract the departing one. Each slide is O(1). Total: O(n).
1def max_sum_window(nums, k):2 """O(n) — incremental update, not re-sum"""3 window_sum = sum(nums[:k]) # One-time O(k)4 max_sum = window_sum56 for i in range(k, len(nums)):7 # O(1) per slide: add new, remove old8 window_sum += nums[i] - nums[i - k]9 max_sum = max(max_sum, window_sum)10 return max_sum11# For n=1,000,000 and k=1000:12# ~1 MILLION operations. Fast.The Fundamental Insight
Two Critical Variations — Know the Difference
Every sliding window problem falls into one of two categories. The first thing you must determine is which one you are dealing with, because the template is different.
Fixed-Size Window
The problem tells you the window size. You slide a window of exactly k elements across the array.
How you recognize it:
- "subarray of size k"
- "substring of length k"
- "window of fixed size"
- "k consecutive elements"
Mental model:
A rigid picture frame sliding across a wall. It always shows exactly k paintings. At each step, one painting enters on the right, one leaves on the left.
Example problems:
Max sum subarray of size k, max average subarray of length k, find all anagrams, permutation in string
Dynamic / Variable-Size Window
The window size is not given. You expand and contract the window to find the optimal size that satisfies a condition.
How you recognize it:
- "longest / shortest subarray"
- "minimum window containing..."
- "at most k distinct"
- "sum >= target"
Mental model:
An accordion or rubber band. It stretches to the right to explore, then contracts from the left to optimize. The right pointer never moves backward.
Example problems:
Min subarray sum ≥ target, longest substring without repeating chars, minimum window substring, fruit into baskets
Quick Decision Rule
The Hidden Prerequisite — When Sliding Window Breaks
Sliding window only works when you can maintain a monotonic relationship between the window state and the window boundaries. Specifically:
- 1Expanding the window must always move the state in one direction (e.g., sum increases, character count increases). This is why sliding window works for sum ≥ target but fails when elements can be negative: adding a negative number makes the sum decrease, so expanding does not guarantee progress.
- 2Shrinking the window must always move the state in the opposite direction. Removing an element from the left must “undo” that element’s contribution. This rules out problems like “maximum product subarray” where removing a negative element could increase or decrease the product depending on the sign.
- 3The window state must be updatable in O(1) per element. If adding or removing an element requires re-scanning the entire window (like finding the median), you need an auxiliary data structure (e.g., two heaps) alongside the sliding window.
The Negative Numbers Trap
Pattern Recognition — When to Use Sliding Window
This is the most important section. Before you write a single line of code, you need to recognize the pattern. Here are the trigger phrases that should make you immediately think “Sliding Window.”
“contiguous subarray”
Find the max sum of a contiguous subarray of size k
“substring with condition”
Find the longest substring without repeating characters
“maximum / minimum of size k”
Maximum average subarray of length k
“smallest subarray with sum >=”
Minimum size subarray sum greater than or equal to target
“at most k distinct”
Longest substring with at most k distinct characters
“permutation / anagram in string”
Check if s2 contains a permutation of s1
“fixed-length window”
Maximum number of vowels in a substring of given length
“shrink until valid”
Minimum window substring containing all characters of t
Decision Flowchart: Should You Use Sliding Window?
IS Sliding Window
- Max sum subarray of size k
- Longest substring without repeating chars
- Minimum window substring
- Count of anagrams of pattern in text
- Max consecutive ones (with k flips)
- Fruits into baskets (at most 2 types)
Is NOT Sliding Window
- Two sum (not contiguous, use hash map)
- Max product subarray (negative numbers flip sign)
- Subarray sum equals k (prefix sum + hash map)
- Longest increasing subsequence (not contiguous)
- Trapping rain water (two pointers / stack)
- Merge intervals (sorting + greedy)
The Core Insight
Visual Walkthrough
Watch the window slide in real-time. Notice how the fixed window always has the same size, while the variable window expands and shrinks to find the optimal answer.
1. Fixed-Size Window
Find the maximum sum of any subarray of size k=3 in [2, 1, 5, 1, 3, 2]. The window maintains exactly 3 elements. At each step, add the incoming element and subtract the outgoing one. Answer: 9 (subarray [5, 1, 3]).
Fixed Window: Max Sum of Size k=3
Step 1 / 42. Variable-Size Window
Find the smallest subarray with sum >= 7 in [2, 1, 5, 1, 3, 2]. Expand right to increase sum, shrink left to minimize length. Answer: 2 (subarray [5, 1, 3] at indices [2..4], length 3 with sum 9).
Variable Window: Smallest Subarray with Sum >= 7
Step 1 / 93. Detailed Walkthrough: Longest Substring Without Repeating Characters
Watch the variable window expand and shrink on "ABCABCD". When a duplicate is found, the left pointer shrinks the window until all characters are unique again.
Initialize window: left=0, right=0. Start with character 'A'. Add 'A' to set. Window = "A".
window="A", set={A}, maxLen=1
Templates — Copy, Adapt, Solve
These are the three templates you need. Every sliding window problem in interviews maps to one of these. Learn them cold, then adapt the condition and state update to the specific problem.
Quick Reference: Which Template Do I Use?
| If you see... | Use... | Example |
|---|---|---|
| “subarray of size k” | Template 1 (Fixed) | Max sum of size k, max avg of length k |
| “shortest/minimum subarray with sum ≥” | Template 2 (Variable) | Min subarray sum, smallest window |
| “longest substring with at most k distinct” | Template 3 (Hash Map) | Fruit into baskets, longest no repeat |
| “permutation / anagram in string” | Template 1 + 3 (Fixed + Map) | Permutation in string, find anagrams |
| “minimum window containing all of t” | Template 2 + 3 (Variable + Map) | Minimum window substring |
Template 1: Fixed-Size Window
Use when the problem specifies the exact window size (k). Build the initial window, then slide by adding one and removing one.
1def fixed_window(nums, k):2 # ---- SECTION 1: BUILD FIRST WINDOW ----3 # WHY sum first k? We need a starting state before we can slide.4 # This avoids edge-case logic inside the main loop.5 window_sum = sum(nums[:k])6 best = window_sum78 # ---- SECTION 2: SLIDE THE WINDOW ----9 # WHY start at k? Indices 0..k-1 are already in the window.10 # Each step adds nums[right] and removes nums[right - k].11 for right in range(k, len(nums)):12 window_sum += nums[right] # Add the new element entering the window13 window_sum -= nums[right - k] # Remove the element leaving the window14 # WHY O(1) per step? We update the sum incrementally instead of15 # re-summing all k elements — this is the entire point of sliding window16 best = max(best, window_sum)1718 # ---- SECTION 3: RETURN ----19 return best20# O(n) time, O(1) spaceTemplate Breakdown
Build First Window
Sum the first k elements. This is the only time we do O(k) work. After this, every slide is O(1). We also initialize best because the first window might be the answer.
Slide the Window
right is the new element entering. right - k is the element leaving. The sum changes by +nums[right] - nums[right-k]. This is why it is O(1) per slide instead of O(k).
Return
After the loop, best holds the maximum sum across all windows of size k.
How to Apply This Template
- Step 1: Identify the window state. For sum problems it is a running total. For frequency problems it is a hash map. For min/max it could be a deque.
- Step 2: Define how the state changes when an element enters (add it) and leaves (subtract/remove it). Both must be O(1).
- Step 3: Decide what to track: max sum? min sum? count of valid windows? Update after each slide.
Template 2: Variable-Size Window (Shrinking)
Use when you need to find the minimum/maximum window that satisfies a condition. Expand right to explore, shrink left while the condition holds.
1def variable_window(nums, target):2 # ---- SECTION 1: INITIALIZATION ----3 left = 04 window_sum = 05 best = float('inf') # WHY inf? We want minimum, so start with worst case67 # ---- SECTION 2: EXPAND (outer loop) ----8 # WHY for-loop on right? Right pointer always moves forward — we never9 # shrink the window from the right side. This guarantees O(n) total.10 for right in range(len(nums)):11 window_sum += nums[right] # Add new element to window state1213 # ---- SECTION 3: SHRINK (inner while) ----14 # WHY while, not if? We may need to shrink multiple times.15 # Example: after adding a large number, several left elements16 # can be removed while the sum still meets the target.17 while window_sum >= target:18 best = min(best, right - left + 1) # Record valid window19 window_sum -= nums[left] # Remove leftmost element20 left += 1 # Shrink from left2122 # ---- SECTION 4: RETURN ----23 return best if best != float('inf') else 024# O(n) time, O(1) spaceTemplate Breakdown
Initialization
best = inf because we want the minimum window size. left = 0 marks the start of the window. The window is always [left, right] inclusive.
Expand (outer for-loop)
The right pointer marches forward one element at a time, adding each element to the window state. This is the “explore” phase.
Shrink (inner while-loop)
Once the condition is met, shrink from the left to find the minimum valid window. The while (not if) is critical — you may need to shrink multiple times in a single expansion step.
Return
If best was never updated, no valid window exists. Return 0 (or “” for string problems).
How to Apply This Template
- Step 1: Define the window state and the validity condition. For “smallest subarray with sum ≥ target,” state = running sum, condition = sum ≥ target.
- Step 2: Decide whether to minimize or maximize. Minimize: shrink while valid, update best inside the while. Maximize: expand while valid, update best outside the while.
- Step 3: Handle edge cases: empty array, no valid window found, target = 0.
Template 3: Sliding Window with Hash Map
Use when you need to track character/element frequencies inside the window. The hash map replaces the simple sum as your window state.
1def window_with_map(s):2 left = 03 char_count = {} # WHY a map? We need to track how many of each4 # character is in the current window. A simple sum5 # cannot capture this — we need per-character counts.6 best = 078 for right in range(len(s)):9 # EXPAND: add the new character to our frequency map10 char_count[s[right]] = char_count.get(s[right], 0) + 11112 # SHRINK: while the window violates our condition, remove from left13 # WHY len(char_count) > 2? This example finds longest substring14 # with at most 2 distinct chars. Change this condition per problem.15 while len(char_count) > 2:16 char_count[s[left]] -= 117 if char_count[s[left]] == 0:18 del char_count[s[left]] # WHY delete zeros? So len(map)19 # accurately reflects the count of DISTINCT characters20 # in the window. A key with value 0 would be a phantom.21 left += 12223 # UPDATE: window is now valid, check if it is the best24 best = max(best, right - left + 1)25 return best26# O(n) time, O(k) space where k = distinct charsTemplate Breakdown
State = Frequency Map
Instead of a running sum, the window state is a hash map counting each character. len(map) tells you how many distinct characters are in the window.
Expand + Shrink
Same expand-then-shrink pattern as Template 2, but state updates involve map insertions/removals instead of additions/subtractions.
Delete Zero-Count Keys
This is the most common bug. When a character count drops to 0, you must delete the key. Otherwise len(map) includes phantom characters that are not actually in the window.
How to Apply This Template
- Step 1: Identify the condition for the while-loop. “At most k distinct” →
len(map) > k. “No repeating chars” →map[s[right]] > 1. - Step 2: Decide if you are maximizing (update best after the while) or minimizing (update best inside the while).
- Step 3: Always delete zero-count keys from the map. This prevents silent bugs in “distinct count” checks.
Variations — The Four Flavors
Every sliding window problem falls into one of these categories. Identify which one you are dealing with, grab the right template, and you are halfway done.
Fixed-Size
Use: Template 1
- Max sum of subarray of size k
- Max average subarray of length k
- Count distinct elements in every window of size k
Variable (Shrinking)
Use: Template 2
- Minimum size subarray sum >= target
- Smallest window containing all chars of t
- Shortest subarray with sum at least k
Character Counting
Use: Template 3
- Longest substring without repeating chars
- Longest substring with at most k distinct
- Permutation in string
Fixed + Hash Map
Use: Templates 1 + 3
- Find all anagrams in a string
- Check inclusion (permutation in string)
- Count occurrences of anagram
Worked Examples
Four problems from easy to hard. Each follows the same structure: problem statement, why sliding window applies, code with inline explanation, and complexity analysis.
1. Maximum Sum Subarray of Size K
Problem: Given an array of integers and a number k, find the maximum sum of any contiguous subarray of size k.
Example: nums = [2, 1, 5, 1, 3, 2], k = 3 → Output: 9 (subarray [5, 1, 3])
Think Before You Code
- Q1:What is the brute force approach? What is its time complexity?
- Q2:When the window slides from position [0..2] to [1..3], which elements change?
- Q3:Can you update the sum in O(1) per slide instead of O(k)?
- Q4:Is the window size fixed or variable here? Which template applies?
Brute Force — O(n × k)
For each starting index i, sum the next k elements. This requires a nested loop — the outer loop picks the starting position, the inner loop computes the sum.
1def max_sum_brute(nums, k):2 max_sum = float('-inf')3 for i in range(len(nums) - k + 1):4 current = 05 for j in range(i, i + k): # <-- This inner loop is the waste6 current += nums[j] # Re-summing elements we already saw7 max_sum = max(max_sum, current)8 return max_sum9# O(n*k) time — for k=1000, n=10^6, that's 10^9 operationsThe “Aha” Moment — From Brute Force to Sliding Window
Look at two consecutive windows: [2, 1, 5] and [1, 5, 1]. They share the elements [1, 5]. The brute force re-sums those shared elements. That is wasted work.
The key realization: new_sum = old_sum - departing_element + arriving_element. Window [1..3] sum = Window [0..2] sum - nums[0] + nums[3] = 8 - 2 + 1 = 7. One subtraction and one addition instead of re-summing 3 elements.
This single observation turns O(n × k) into O(n). You compute the first window sum in O(k), then each subsequent window in O(1). Total: O(k) + O(n-k) = O(n).
Why Sliding Window?
1def max_sum_subarray(nums, k):2 if len(nums) < k: return 0 # WHY guard? Can't form a window of size k3 window_sum = sum(nums[:k]) # Build the first window: O(k) one-time cost4 max_sum = window_sum # First window might be the answer56 for i in range(k, len(nums)):7 # WHY +nums[i] - nums[i-k]? nums[i] enters the window,8 # nums[i-k] leaves it. Net change in sum — O(1) per slide.9 window_sum += nums[i] - nums[i - k]10 max_sum = max(max_sum, window_sum)11 return max_sum # O(n) time, O(1) spaceDry Run: nums = [2, 1, 5, 1, 3, 2], k = 3
| Window | Elements | Sum | maxSum |
|---|---|---|---|
| [0..2] | 2, 1, 5 | 8 | 8 |
| [1..3] | 1, 5, 1 | 8-2+1=7 | 8 |
| [2..4] | 5, 1, 3 | 7-1+3=9 | 9 |
| [3..5] | 1, 3, 2 | 9-5+2=6 | 9 |
Key Line Spotlight
window_sum += nums[i] - nums[i - k]This single line is the entire optimization. Instead of recalculating the sum from scratch each time (O(k)), we compute the net change in O(1). The element at index i enters the window, and the element at i - k leaves.
Why O(n) Time?
- Building first window:
sum(nums[:k])takes O(k). - Sliding loop:
for i in range(k, len(nums))runs n-k times, each doing O(1) work. - Total: O(k) + O(n-k) = O(n). Compared to brute force O(n*k).
Common Mistakes & Edge Cases
- k > len(nums): Cannot form a window of size k. Return 0 or handle as edge case. Always add the guard clause.
- k = len(nums): Only one window exists. The initial sum is the answer. The for-loop body never executes.
- All negative numbers: The algorithm still works because we compare all window sums and pick the max. Do not add special cases for negatives here — the fixed-window approach handles it naturally.
- Off-by-one on loop start: The loop starts at
k, notk-1ork+1. Index k is the first new element entering the window after the initial window [0..k-1].
2. Longest Substring Without Repeating Characters
Problem: Given a string s, find the length of the longest substring without repeating characters.
Example: s = "abcabcbb" → Output: 3 ("abc")
Think Before You Code
- Q1:Is the window size fixed or variable? (Variable — we are looking for the “longest”)
- Q2:What makes a window invalid? (A repeated character inside the window)
- Q3:When we find a duplicate, how far should we move the left pointer? (Past the previous occurrence)
- Q4:Should we use a set or a map? (Map — we need the index of the last occurrence for the jump optimization)
Brute Force — O(n³)
Check every possible substring (n² substrings), and for each one, check if all characters are unique (O(n) per check). Total: O(n³). Absolutely unacceptable for interview-length strings.
1def longest_brute(s):2 best = 03 for i in range(len(s)): # O(n)4 for j in range(i, len(s)): # O(n)5 substr = s[i:j+1]6 if len(set(substr)) == len(substr): # O(n) unique check7 best = max(best, j - i + 1)8 else:9 break # Minor optimization: if duplicate found, stop10 return best11# O(n^3) worst case — far too slowThe “Aha” Moment
In the brute force, when we find a duplicate at position j, we throw away all our work and start over from i+1. But think about it: if the substring [i..j] has a duplicate (say character at position p equals character at position j), then all substrings starting at i through p will also have that duplicate. We can jump left directly to p+1.
The insight: Store the last-seen index of each character. When we encounter a duplicate, move left to last_seen[char] + 1 in one step. No need to shrink one-by-one.
Why Sliding Window?
1def length_of_longest_substring(s):2 char_index = {} # WHY map not set? We store the INDEX, not just presence.3 # This lets us jump left directly instead of sliding 1-by-1.4 left, best = 0, 056 for right in range(len(s)):7 # WHY check >= left? The char might have been seen before, but if its8 # last index is BEFORE our window (< left), it is not a duplicate in9 # the current window. We only care about duplicates inside [left, right].10 if s[right] in char_index and char_index[s[right]] >= left:11 left = char_index[s[right]] + 1 # WHY +1? Jump PAST the duplicate12 char_index[s[right]] = right # Always update to latest index13 best = max(best, right - left + 1) # Window [left..right] is valid14 return best # O(n) time, O(min(n, 26)) spaceDry Run: s = "abcabcbb"
| right | char | left | window | best |
|---|---|---|---|---|
| 0 | a | 0 | "a" | 1 |
| 1 | b | 0 | "ab" | 2 |
| 2 | c | 0 | "abc" | 3 |
| 3 | a | 1 | "bca" | 3 |
| 4 | b | 2 | "cab" | 3 |
| 5 | c | 3 | "abc" | 3 |
Red chars triggered a left jump. Best = 3.
Key Line Spotlight
if s[right] in char_index and char_index[s[right]] >= leftThe >= left check is the subtle part. A character may exist in our map from a previous window position that is now before our left boundary. Without this check, we would falsely detect a duplicate and skip a valid window.
Common Mistakes & Edge Cases
- Forgetting the ≥ left check: Without
char_index[s[right]] >= left, you might jump left backward to an index before the current left pointer. The map contains stale entries from earlier windows. - Empty string: If s is empty, the loop never executes, and best stays 0. This is handled naturally.
- All same characters: s = "aaaa" → the window never grows beyond size 1. Each step, left jumps to right’s position. Answer: 1.
- Using a set instead of a map: A set can work (shrink left one-by-one until the duplicate is removed), but it is O(2n) vs the map approach which is O(n) with the jump optimization. In interviews, the map approach is preferred.
3. Minimum Window Substring
Problem: Given strings s and t, find the minimum window in s that contains all characters of t (including duplicates).
Example: s = "ADOBECODEBANC", t = "ABC" → Output: "BANC"
Think Before You Code
- Q1:What does “contains all characters of t” mean precisely? (Each character in t at its required frequency, including duplicates)
- Q2:Are we minimizing or maximizing the window? (Minimizing — shrink from the left while the window is valid)
- Q3:How do we efficiently check if all characters of t are in the current window? (Use a “formed” counter instead of comparing full maps)
- Q4:Why use
==instead of>=when incrementing the formed counter? (To avoid counting the same character multiple times)
Brute Force — O(n² × |t|)
For each starting index i, expand right until all characters of t are included. Then record the window length. The next starting index i+1 starts from scratch. This re-checks character containment for every window, wasting massive computation.
The “Aha” Moment
Once we find a valid window, we do not need to start over from the next index. Instead, shrink from the left to see if a smaller valid window exists. When the window becomes invalid (a required character is lost), expand right again. The key optimization is tracking a have_cnt / formed integer that counts how many unique characters in t have been fully satisfied in the window. This turns the “is the window valid?” check from O(|t|) to O(1).
The Hard Part
1from collections import Counter23def min_window(s, t):4 if not t or not s:5 return ""6 need = Counter(t) # What we need: char -> required count7 have = {} # What we have: char -> current count in window8 need_cnt, have_cnt = len(need), 0 # WHY len(need)? Count UNIQUE chars needed9 left, best = 0, (float('inf'), 0, 0)1011 for right in range(len(s)):12 ch = s[right]13 have[ch] = have.get(ch, 0) + 1 # Add char to our window1415 # WHY == not >=? We only increment have_cnt at the exact moment16 # a character reaches its required frequency. If we used >=, we17 # would count it multiple times as more duplicates arrive.18 if ch in need and have[ch] == need[ch]:19 have_cnt += 12021 # SHRINK: when all required chars are satisfied, try to minimize22 while have_cnt == need_cnt:23 if right - left + 1 < best[0]:24 best = (right - left + 1, left, right)25 out = s[left]26 have[out] -= 127 # WHY check < need[out]? We just dropped below the required28 # count for this character — the window is no longer valid29 if out in need and have[out] < need[out]:30 have_cnt -= 131 left += 13233 return s[best[1]:best[2]+1] if best[0] != float('inf') else ""34# Time: O(|s| + |t|) Space: O(|s| + |t|)Key Line Spotlight
if ch in need and have[ch] == need[ch]: have_cnt += 1This line uses == instead of >= for a crucial reason: we only want to increment have_cnt at the exact moment a character reaches its target frequency. Using >= would over-count and break the shrinking logic. This is the most common bug in this problem.
Why O(|s| + |t|)?
- Building
need: O(|t|) to count character frequencies in t. - Outer loop:
rightmoves from 0 to |s|-1 → O(|s|). - Inner while:
leftmoves at most |s| times total across all iterations of the outer loop → O(|s|) amortized. - Total: O(|s| + |t|). Each character is added once and removed at most once.
Dry Run: s = "ADOBECODEBANC", t = "ABC"
| right | char | left | formed | window | best |
|---|---|---|---|---|---|
| 0 | A | 0 | 1/3 | "A" | - |
| 3 | B | 0 | 2/3 | "ADOB" | - |
| 5 | C | 0 | 3/3 | "ADOBEC" | 6 |
| 5 | shrink | 1 | 2/3 | "DOBEC" | 6 |
| ... | expand | ... | ... | ... | 6 |
| 10 | A | 9 | 3/3 | "BANC" | 4 |
Final answer: “BANC” (length 4). The window shrinks aggressively once all characters are satisfied.
Common Mistakes & Edge Cases
- Using >= instead of == for formed counter: If you use
have[ch] >= need[ch], you increment formed every time a duplicate arrives, not just when the required frequency is first met. This over-counts and the shrinking logic breaks. - Not handling duplicate characters in t: If t = "AABC", you need two A’s, not just one. The
needmap correctly handles this because it counts frequencies. - t is longer than s: Return empty string immediately. No window in s can contain all characters of t.
- Recording best inside vs outside the while loop: Since we are minimizing, record best inside the while loop (before shrinking). If you record outside, you get the window size after it has already become invalid.
4. Permutation in String
Problem: Given two strings s1 and s2, return true if s2 contains a permutation of s1. In other words, return true if one of s1's permutations is a substring of s2.
Example: s1 = "ab", s2 = "eidbaooo" → Output: true ("ba" is a permutation of "ab")
Think Before You Code
- Q1:What defines a permutation? (Same characters with same frequencies)
- Q2:Is the window size fixed or variable? (Fixed — a permutation of s1 has exactly s1.length characters)
- Q3:How do we check if two frequency maps are equal without comparing all 26 slots each time? (Track a “matches” counter)
- Q4:What is the relationship between this problem and “Find All Anagrams”? (Same approach — anagram = permutation. One returns boolean, the other returns all starting indices)
The Idea
A permutation has the same character frequencies as the original. Slide a fixed-size window of length s1.length over s2 and compare frequency maps. The JS solution optimizes further: instead of comparing two 26-element arrays each step, track a matches counter that counts how many of the 26 slots are equal. When matches == 26, all frequencies match.
Why Sliding Window?
1def check_inclusion(s1, s2):2 if len(s1) > len(s2):3 return False4 s1_count, win_count = {}, {}5 for ch in s1:6 s1_count[ch] = s1_count.get(ch, 0) + 1 # Frequency map of target7 k = len(s1) # WHY fixed size k? A permutation has the same length89 for i in range(len(s2)):10 ch = s2[i]11 win_count[ch] = win_count.get(ch, 0) + 1 # Add incoming char1213 if i >= k: # WHY i >= k? Once we have more than k chars, remove the oldest14 left_ch = s2[i - k]15 win_count[left_ch] -= 116 if win_count[left_ch] == 0:17 del win_count[left_ch] # WHY delete? So map equality works correctly1819 # WHY direct map comparison? Two maps are equal iff they have the same20 # keys with the same values — exactly the definition of "same frequencies"21 if win_count == s1_count:22 return True23 return False24# Time: O(n), Space: O(1) -- at most 26 keysKey Line Spotlight
if (winCount[ri] === s1Count[ri]) matches++; else if (winCount[ri] === s1Count[ri] + 1) matches--;This is the O(1) optimization that makes the JS solution elegant. When the incoming character makes the window count equal to the target count, we gain a match. When it was previously equal and now overshoots by 1, we lose a match. This replaces an O(26) array comparison with O(1) bookkeeping.
Why O(n) Time, O(1) Space?
- Time: The loop runs |s2| - k times. Each step does O(1) work (one addition, one removal, constant match updates). Python version: O(26) per step for map comparison, but 26 is a constant.
- Space: Only 26-element arrays (or maps with at most 26 keys). This is O(1) regardless of input size.
5. Fruit Into Baskets (Longest Subarray with At Most 2 Distinct)
Problem: You have a row of trees. Each tree produces a type of fruit (represented as an integer). You have two baskets, and each basket can hold only one type of fruit. Starting from any tree, you pick one fruit from each tree moving right. You stop when you would need a third type. Find the maximum number of fruits you can collect.
Translation: Find the longest contiguous subarray with at most 2 distinct values.
Example: fruits = [1, 2, 3, 2, 2] → Output: 4 (subarray [2, 3, 2, 2])
Think Before You Code
- Q1:What is the real constraint? (“At most 2 distinct values” in a contiguous subarray)
- Q2:Are we minimizing or maximizing? (Maximizing — longest subarray)
- Q3:When the window has 3 distinct values, what do we do? (Shrink from the left until we are back to 2 distinct)
- Q4:Which template does this map to? (Template 3: sliding window with hash map)
Brute Force — O(n²)
For each starting index, extend right counting distinct values. Stop when you hit a third distinct value. Track the maximum window length. O(n) starts × O(n) extension = O(n²).
The “Aha” Moment
This is literally Template 3 with the condition len(count) > 2. When the window has more than 2 distinct types, shrink from the left. When you remove an element and its count drops to 0, delete it from the map so len(count)accurately reflects distinct types. Update best after shrinking since we want the maximum.
Pattern Recognition
len(map) > K. This problem is K=2. “Longest Substring with At Most K Distinct Characters” is the exact same code with K as a parameter.1def total_fruit(fruits):2 count = {} # fruit_type -> frequency in window3 left = 04 best = 056 for right in range(len(fruits)):7 # EXPAND: add the new fruit to our basket8 f = fruits[right]9 count[f] = count.get(f, 0) + 11011 # SHRINK: if we have more than 2 types, remove from left12 while len(count) > 2:13 left_f = fruits[left]14 count[left_f] -= 115 if count[left_f] == 0:16 del count[left_f] # Critical: remove empty types17 left += 11819 # UPDATE: window is valid (at most 2 types), track max length20 best = max(best, right - left + 1)2122 return best23# O(n) time, O(1) space (at most 3 keys in map)Dry Run: fruits = [1, 2, 3, 2, 2]
| right | fruit | left | count | window | best |
|---|---|---|---|---|---|
| 0 | 1 | 0 | {1:1} | [1] | 1 |
| 1 | 2 | 0 | {1:1, 2:1} | [1,2] | 2 |
| 2 | 3 | 1 | {2:1, 3:1} | [2,3] | 2 |
| 3 | 2 | 1 | {2:2, 3:1} | [2,3,2] | 3 |
| 4 | 2 | 1 | {2:3, 3:1} | [2,3,2,2] | 4 |
When fruit 3 enters at index 2, we shrink to remove fruit 1. Answer: 4.
Key Line Spotlight
while len(count) > 2This single condition is the heart of the problem. The map’s length tells you how many distinct fruit types are in the window. When it exceeds 2, shrink until you are back to 2 or fewer. The “at most K distinct” family of problems all share this structure — just change the 2 to K.
Common Mistakes
- Not deleting zero-count keys: If you decrement a count to 0 but do not delete the key,
len(count)still includes it as a “distinct” type. This causes the window to shrink too aggressively. - Updating best inside the while instead of after: Since we are maximizing, we want the largest valid window. Update best after the while loop ensures the window is valid.
6. Minimum Size Subarray Sum
Problem: Given an array of positive integers and a target, find the minimal length of a subarray whose sum is greater than or equal to target. If no such subarray exists, return 0.
Example: nums = [2, 3, 1, 2, 4, 3], target = 7 → Output: 2 (subarray [4, 3])
Think Before You Code
- Q1:Is the window size fixed or variable? (Variable — we want the minimum length)
- Q2:When is the window valid? (When sum ≥ target)
- Q3:Why can we use sliding window here? (All values are positive, so expanding always increases the sum. The monotonic property holds.)
- Q4:Which template? (Template 2: variable window, shrink while valid)
The “Aha” Moment
Since all numbers are positive, once the window sum ≥ target, we know that expanding further will only make the sum bigger (and the window longer). So we should shrink from the left to see how small we can make the window while keeping it valid. This is Template 2 applied directly.
1def min_subarray_len(target, nums):2 left = 03 window_sum = 04 best = float('inf') # Want minimum — start with worst case56 for right in range(len(nums)):7 window_sum += nums[right] # Expand: add new element89 # Shrink: while sum >= target, try to minimize10 while window_sum >= target:11 best = min(best, right - left + 1) # Record valid window12 window_sum -= nums[left] # Remove leftmost13 left += 1 # Shrink1415 return best if best != float('inf') else 016# O(n) time, O(1) spaceDry Run: nums = [2, 3, 1, 2, 4, 3], target = 7
| right | add | sum | left | action | best |
|---|---|---|---|---|---|
| 0 | +2 | 2 | 0 | 2 < 7, expand | inf |
| 1 | +3 | 5 | 0 | 5 < 7, expand | inf |
| 2 | +1 | 6 | 0 | 6 < 7, expand | inf |
| 3 | +2 | 8 | 0 | 8 ≥ 7, record 4, shrink | 4 |
| 3 | -2 | 6 | 1 | 6 < 7, stop shrink | 4 |
| 4 | +4 | 10 | 1 | 10 ≥ 7, record 4, shrink | 4 |
| 4 | -3 | 7 | 2 | 7 ≥ 7, record 3, shrink | 3 |
| 4 | -1 | 6 | 3 | 6 < 7, stop | 3 |
| 5 | +3 | 9 | 3 | 9 ≥ 7, record 3, shrink to [4,3] sum=7, record 2 | 2 |
Answer: 2 (subarray [4, 3]).
Why while, Not if?
At right=5, the sum is 9. We shrink once (remove 2, sum=7, length=3). Still ≥ 7, so we shrink again (remove 4... wait, that would make sum=3). Actually we record length 3 first, then remove the left element. The while loop may execute 2-3 times in a single expansion step. Across all iterations, left moves at most n times, so total work is O(n).
Common Mistakes & Pro Tips
These are the exact mistakes that cost candidates offers. Read them before your interview. Each one comes from real interview debriefs.
Common Mistakes
- Forgetting to shrink the window. You expand the window happily but never shrink it. Result: you track the maximum of nothing useful. Always ask: “When should I move the left pointer?”
- Wrong condition for shrinking. Using
ifinstead ofwhilefor variable-size windows. You might need to shrink multiple times in a single step. Always usewhilefor variable windows. - Not handling empty input. If the array is empty or k is 0, your window initialization will crash. Always add a guard clause at the top.
- Not cleaning up zero counts in hash maps. When a character count hits 0, delete the key. Otherwise
len(map)ormap.sizegives wrong results for “distinct character” problems. - Off-by-one on window boundaries. The window is [left, right] inclusive. Length = right - left + 1. When removing the leftmost element in a fixed window, it is
nums[right - k], notnums[right - k + 1].
Pro Tip: Start Brute Force, Then Optimize
Pro Tip: Verbalize the Window State
Pro Tip: Time Complexity Proof
Interviewer's Perspective
What Interviewers Are Really Testing
- 1.Can you identify fixed vs variable window? This is the first fork in the road. Fixed window means the window size is given (max sum of k elements). Variable window means you are optimizing the window size itself (shortest subarray with sum ≥ target). Getting this wrong leads to a completely different template.
- 2.Can you explain why it is O(n) and not O(n²)? The most common follow-up. Interviewers want to hear: “Each element enters the window exactly once and leaves exactly once. The inner shrink loop does not restart from scratch — across all iterations, left moves at most n times total. So total work is O(2n) = O(n).”
- 3.Do you know when to use a hash map inside the window? Problems like “longest substring without repeating characters” or “minimum window substring” require tracking character frequencies. The interviewer wants to see you combine sliding window with a hash map naturally, not treat it as a separate technique.
- 4.Can you handle the “at most K distinct” pattern? This is a common variant: “longest substring with at most K distinct characters.” The trick is knowing that “exactly K” = “at most K” minus “at most K-1.” Mentioning this decomposition shows depth.
Follow-up Questions Interviewers Love
After Fixed Window Problems
- • “What if the window size is not given? How would you find the optimal k?” — Switch to variable window or binary search on k.
- • “Can you do this with a stream instead of an array?” — Fixed window naturally works on streams since you only need the last k elements.
- • “What about a circular array?” — Double the array or use modular arithmetic. The window logic stays the same.
After Variable Window Problems
- • “What if elements can be negative?” — Sliding window for sum-based problems breaks with negatives because shrinking the window does not guarantee a smaller sum. Use prefix sums + deque instead.
- • “What about exactly K distinct characters?” — Decompose: atMost(K) - atMost(K-1). This is a must-know trick.
- • “Can you return the actual substring, not just the length?” — Track start index and length of the best window, then slice at the end. Do not build strings in the loop.
Interview Cheat Sheet — Your 30-Second Recap
Read this the morning of your interview. It contains everything you need to recognize, template, and solve any sliding window problem.
Recognize the Pattern
Trigger words: “contiguous subarray,” “substring,” “window of size k,” “at most k distinct,” “smallest/longest with condition.” Must be on contiguous elements. Must have a monotonic property (expand increases state, shrink decreases it).
Fixed or Variable?
Is the window size given? Fixed → Template 1. Are you finding the optimal window size? Variable → Template 2/3.
Define Your Window State
Sum? Frequency map? Count of distinct elements? Count of satisfied characters? The state must be updatable in O(1) when adding/removing one element.
Write the Loop
Outer for-loop on right (always moves forward). Inner logic: Fixed → add new, remove old, update best. Variable → add new, while condition, shrink + update best.
Explain the Complexity
“Each element enters the window at most once and leaves at most once. The inner while-loop does not run n times per outer iteration — across all iterations, left moves at most n times total. O(2n) = O(n).”
Complexity Comparison — All Problems at a Glance
| Problem | Brute Force | Sliding Window | Template |
|---|---|---|---|
| Max Sum Subarray of Size K | O(n*k) | O(n) | 1 (Fixed) |
| Longest Substring No Repeat | O(n³) | O(n) | 3 (Map) |
| Minimum Window Substring | O(n²*|t|) | O(n+|t|) | 2+3 |
| Permutation in String | O(n*k*26) | O(n) | 1+3 |
| Fruit Into Baskets | O(n²) | O(n) | 3 (Map) |
| Min Size Subarray Sum | O(n²) | O(n) | 2 (Variable) |
Related Patterns
Two Pointers
Sliding window is a special case of two pointers where both pointers move in the same direction. If you understand two pointers, sliding window is a natural extension.
Study PatternHeap Patterns
Some advanced sliding window problems (like “sliding window maximum”) combine a window with a heap or monotonic deque to track the max/min efficiently.
Study PatternArrays & Strings Concept
Review array operations and string immutability. Sliding window operates on contiguous subarrays/substrings, so array fundamentals are essential.
Review ConceptHash Maps Concept
Dynamic sliding window problems often use a hash map to track character frequencies within the window (e.g., minimum window substring).
Review Concept