MediumFrequency: Very HighGoogle, Amazon, Microsoft, Meta

Sliding Window

Imagine looking through a window on a train — as the train moves, the view shifts one frame at a time. That is exactly how sliding window processes subarrays. Instead of recalculating from scratch, you update the answer incrementally by adding the new element entering the window and removing the one leaving it. O(n*k) brute force becomes O(n).

Fixed Window (k=3) Sliding Across Array

Sum = 8
Window slides right: add new element, remove old element2[0]1[1]5[2]1[3]3[4]2[5]Window [0..2] = 2 + 1 + 5 = 8

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).

brute-force-approach
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 scratch
7 current_sum = 0
8 for j in range(i, i + k):
9 current_sum += nums[j]
10 max_sum = max(max_sum, current_sum)
11 return max_sum
12# 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).

sliding-window-approach
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_sum
5
6 for i in range(k, len(nums)):
7 # O(1) per slide: add new, remove old
8 window_sum += nums[i] - nums[i - k]
9 max_sum = max(max_sum, window_sum)
10 return max_sum
11# For n=1,000,000 and k=1000:
12# ~1 MILLION operations. Fast.

The Fundamental Insight

Consecutive windows of size k share k-1 elements. The only difference between window [i..i+k-1] and window [i+1..i+k] is one element leaving (index i) and one element entering (index i+k). Recalculating from scratch ignores this overlap. Sliding window exploits it.

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.

1

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

2

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

Is the window size given to you? If yes → fixed-size window (Template 1). If no → variable-size window (Template 2 or 3). This single question determines which template to use. Get it right and the code writes itself.

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

“Given an array with negative numbers, find the smallest subarray with sum ≥ target.” Many candidates instinctively reach for sliding window. It will not work. With negative numbers, expanding the window can decrease the sum, and shrinking can increase it. The monotonic relationship breaks. Use prefix sums + deque or binary search instead.

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?

Contiguous subarray / substring?YESIs the window size fixed or variable?FIXEDVARIABLEFixed-Size TemplateMax sum of size k,max average, anagram checkVariable-Size TemplateMin subarray sum, longestsubstring, min window substrNONot SlidingWindowNeed character counting?Add a hash map to your 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

Sliding window works when you can maintain a running state (sum, count, frequency map) that can be updated in O(1) when you add/remove a single element. If adding or removing an element requires re-scanning the window, sliding window will not help.

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 / 4
2[0]1[1]5[2]1[3]3[4]2[5]sum = 2 + 1 + 5 = 8

2. 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 / 9
2[0]1[1]5[2]1[3]3[4]2[5]Expand: sum=2 < 7 | best = none

3. 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.

1 / 8
A[0]B[1]C[2]A[3]B[4]C[5]D[6]LRSet: {A}maxLen = 1window = "A"

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.

fixed-size-window-template
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_sum
7
8 # ---- 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 window
13 window_sum -= nums[right - k] # Remove the element leaving the window
14 # WHY O(1) per step? We update the sum incrementally instead of
15 # re-summing all k elements — this is the entire point of sliding window
16 best = max(best, window_sum)
17
18 # ---- SECTION 3: RETURN ----
19 return best
20# O(n) time, O(1) space

Template Breakdown

1

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.

2

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).

3

Return

After the loop, best holds the maximum sum across all windows of size k.

How to Apply This Template

  1. 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.
  2. Step 2: Define how the state changes when an element enters (add it) and leaves (subtract/remove it). Both must be O(1).
  3. 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.

variable-size-window-template
1def variable_window(nums, target):
2 # ---- SECTION 1: INITIALIZATION ----
3 left = 0
4 window_sum = 0
5 best = float('inf') # WHY inf? We want minimum, so start with worst case
6
7 # ---- SECTION 2: EXPAND (outer loop) ----
8 # WHY for-loop on right? Right pointer always moves forward — we never
9 # 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 state
12
13 # ---- 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 elements
16 # can be removed while the sum still meets the target.
17 while window_sum >= target:
18 best = min(best, right - left + 1) # Record valid window
19 window_sum -= nums[left] # Remove leftmost element
20 left += 1 # Shrink from left
21
22 # ---- SECTION 4: RETURN ----
23 return best if best != float('inf') else 0
24# O(n) time, O(1) space

Template Breakdown

1

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.

2

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.

3

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.

4

Return

If best was never updated, no valid window exists. Return 0 (or “” for string problems).

How to Apply This Template

  1. Step 1: Define the window state and the validity condition. For “smallest subarray with sum ≥ target,” state = running sum, condition = sum ≥ target.
  2. 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.
  3. 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.

sliding-window-hashmap-template
1def window_with_map(s):
2 left = 0
3 char_count = {} # WHY a map? We need to track how many of each
4 # character is in the current window. A simple sum
5 # cannot capture this — we need per-character counts.
6 best = 0
7
8 for right in range(len(s)):
9 # EXPAND: add the new character to our frequency map
10 char_count[s[right]] = char_count.get(s[right], 0) + 1
11
12 # SHRINK: while the window violates our condition, remove from left
13 # WHY len(char_count) > 2? This example finds longest substring
14 # with at most 2 distinct chars. Change this condition per problem.
15 while len(char_count) > 2:
16 char_count[s[left]] -= 1
17 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 characters
20 # in the window. A key with value 0 would be a phantom.
21 left += 1
22
23 # UPDATE: window is now valid, check if it is the best
24 best = max(best, right - left + 1)
25 return best
26# O(n) time, O(k) space where k = distinct chars

Template Breakdown

1

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.

2

Expand + Shrink

Same expand-then-shrink pattern as Template 2, but state updates involve map insertions/removals instead of additions/subtractions.

3

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

  1. Step 1: Identify the condition for the while-loop. “At most k distinct” → len(map) > k. “No repeating chars” → map[s[right]] > 1.
  2. Step 2: Decide if you are maximizing (update best after the while) or minimizing (update best inside the while).
  3. 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.

Easy

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

  1. Q1:What is the brute force approach? What is its time complexity?
  2. Q2:When the window slides from position [0..2] to [1..3], which elements change?
  3. Q3:Can you update the sum in O(1) per slide instead of O(k)?
  4. 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.

brute-force-max-sum
1def max_sum_brute(nums, k):
2 max_sum = float('-inf')
3 for i in range(len(nums) - k + 1):
4 current = 0
5 for j in range(i, i + k): # <-- This inner loop is the waste
6 current += nums[j] # Re-summing elements we already saw
7 max_sum = max(max_sum, current)
8 return max_sum
9# O(n*k) time — for k=1000, n=10^6, that's 10^9 operations

The “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?

The key phrase is “contiguous subarray of size k” — fixed-size window. Instead of summing k elements for each position (O(n*k)), slide the window and update the sum in O(1).
max-sum-subarray-of-size-k
1def max_sum_subarray(nums, k):
2 if len(nums) < k: return 0 # WHY guard? Can't form a window of size k
3 window_sum = sum(nums[:k]) # Build the first window: O(k) one-time cost
4 max_sum = window_sum # First window might be the answer
5
6 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) space

Dry Run: nums = [2, 1, 5, 1, 3, 2], k = 3

WindowElementsSummaxSum
[0..2]2, 1, 588
[1..3]1, 5, 18-2+1=78
[2..4]5, 1, 37-1+3=99
[3..5]1, 3, 29-5+2=69

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).
Time: O(n)Space: O(1)

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, not k-1 or k+1. Index k is the first new element entering the window after the initial window [0..k-1].
Medium

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

  1. Q1:Is the window size fixed or variable? (Variable — we are looking for the “longest”)
  2. Q2:What makes a window invalid? (A repeated character inside the window)
  3. Q3:When we find a duplicate, how far should we move the left pointer? (Past the previous occurrence)
  4. 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.

brute-force-longest-substr
1def longest_brute(s):
2 best = 0
3 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 check
7 best = max(best, j - i + 1)
8 else:
9 break # Minor optimization: if duplicate found, stop
10 return best
11# O(n^3) worst case — far too slow

The “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?

“Longest substring” + “without repeating” = variable-size window with a hash set/map. Expand right to include new characters, shrink left when a duplicate is found.
longest-substring-without-repeating
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, 0
5
6 for right in range(len(s)):
7 # WHY check >= left? The char might have been seen before, but if its
8 # last index is BEFORE our window (< left), it is not a duplicate in
9 # 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 duplicate
12 char_index[s[right]] = right # Always update to latest index
13 best = max(best, right - left + 1) # Window [left..right] is valid
14 return best # O(n) time, O(min(n, 26)) space

Dry Run: s = "abcabcbb"

rightcharleftwindowbest
0a0"a"1
1b0"ab"2
2c0"abc"3
3a1"bca"3
4b2"cab"3
5c3"abc"3

Red chars triggered a left jump. Best = 3.

Key Line Spotlight

if s[right] in char_index and char_index[s[right]] >= left

The >= 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.

Time: O(n)Space: O(min(n, 26))

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.
Hard

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

  1. Q1:What does “contains all characters of t” mean precisely? (Each character in t at its required frequency, including duplicates)
  2. Q2:Are we minimizing or maximizing the window? (Minimizing — shrink from the left while the window is valid)
  3. 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)
  4. 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

Track how many of t's characters are satisfied using a formed counter. When formed equals the number of unique characters needed, shrink from the left to minimize. This avoids comparing full frequency maps at every step.
minimum-window-substring
1from collections import Counter
2
3def min_window(s, t):
4 if not t or not s:
5 return ""
6 need = Counter(t) # What we need: char -> required count
7 have = {} # What we have: char -> current count in window
8 need_cnt, have_cnt = len(need), 0 # WHY len(need)? Count UNIQUE chars needed
9 left, best = 0, (float('inf'), 0, 0)
10
11 for right in range(len(s)):
12 ch = s[right]
13 have[ch] = have.get(ch, 0) + 1 # Add char to our window
14
15 # WHY == not >=? We only increment have_cnt at the exact moment
16 # a character reaches its required frequency. If we used >=, we
17 # would count it multiple times as more duplicates arrive.
18 if ch in need and have[ch] == need[ch]:
19 have_cnt += 1
20
21 # SHRINK: when all required chars are satisfied, try to minimize
22 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] -= 1
27 # WHY check < need[out]? We just dropped below the required
28 # count for this character — the window is no longer valid
29 if out in need and have[out] < need[out]:
30 have_cnt -= 1
31 left += 1
32
33 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 += 1

This 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: right moves from 0 to |s|-1 → O(|s|).
  • Inner while: left moves 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.
Time: O(|s| + |t|)Space: O(|s| + |t|)

Dry Run: s = "ADOBECODEBANC", t = "ABC"

rightcharleftformedwindowbest
0A01/3"A"-
3B02/3"ADOB"-
5C03/3"ADOBEC"6
5shrink12/3"DOBEC"6
...expand.........6
10A93/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 need map 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.
Medium

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

  1. Q1:What defines a permutation? (Same characters with same frequencies)
  2. Q2:Is the window size fixed or variable? (Fixed — a permutation of s1 has exactly s1.length characters)
  3. Q3:How do we check if two frequency maps are equal without comparing all 26 slots each time? (Track a “matches” counter)
  4. 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?

A permutation is just a rearrangement — same characters, same frequencies. Use a fixed-size window of length s1.length sliding over s2, comparing frequency maps. When the maps match, you have found a permutation.
permutation-in-string
1def check_inclusion(s1, s2):
2 if len(s1) > len(s2):
3 return False
4 s1_count, win_count = {}, {}
5 for ch in s1:
6 s1_count[ch] = s1_count.get(ch, 0) + 1 # Frequency map of target
7 k = len(s1) # WHY fixed size k? A permutation has the same length
8
9 for i in range(len(s2)):
10 ch = s2[i]
11 win_count[ch] = win_count.get(ch, 0) + 1 # Add incoming char
12
13 if i >= k: # WHY i >= k? Once we have more than k chars, remove the oldest
14 left_ch = s2[i - k]
15 win_count[left_ch] -= 1
16 if win_count[left_ch] == 0:
17 del win_count[left_ch] # WHY delete? So map equality works correctly
18
19 # WHY direct map comparison? Two maps are equal iff they have the same
20 # keys with the same values — exactly the definition of "same frequencies"
21 if win_count == s1_count:
22 return True
23 return False
24# Time: O(n), Space: O(1) -- at most 26 keys

Key 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.
Time: O(n)Space: O(1)
Medium

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

  1. Q1:What is the real constraint? (“At most 2 distinct values” in a contiguous subarray)
  2. Q2:Are we minimizing or maximizing? (Maximizing — longest subarray)
  3. Q3:When the window has 3 distinct values, what do we do? (Shrink from the left until we are back to 2 distinct)
  4. 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

Any time you see “at most K distinct,” think Template 3 with condition 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.
fruit-into-baskets
1def total_fruit(fruits):
2 count = {} # fruit_type -> frequency in window
3 left = 0
4 best = 0
5
6 for right in range(len(fruits)):
7 # EXPAND: add the new fruit to our basket
8 f = fruits[right]
9 count[f] = count.get(f, 0) + 1
10
11 # SHRINK: if we have more than 2 types, remove from left
12 while len(count) > 2:
13 left_f = fruits[left]
14 count[left_f] -= 1
15 if count[left_f] == 0:
16 del count[left_f] # Critical: remove empty types
17 left += 1
18
19 # UPDATE: window is valid (at most 2 types), track max length
20 best = max(best, right - left + 1)
21
22 return best
23# O(n) time, O(1) space (at most 3 keys in map)

Dry Run: fruits = [1, 2, 3, 2, 2]

rightfruitleftcountwindowbest
010{1:1}[1]1
120{1:1, 2:1}[1,2]2
231{2:1, 3:1}[2,3]2
321{2:2, 3:1}[2,3,2]3
421{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) > 2

This 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.
Time: O(n)Space: O(1)
Medium

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

  1. Q1:Is the window size fixed or variable? (Variable — we want the minimum length)
  2. Q2:When is the window valid? (When sum ≥ target)
  3. Q3:Why can we use sliding window here? (All values are positive, so expanding always increases the sum. The monotonic property holds.)
  4. 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.

min-size-subarray-sum
1def min_subarray_len(target, nums):
2 left = 0
3 window_sum = 0
4 best = float('inf') # Want minimum — start with worst case
5
6 for right in range(len(nums)):
7 window_sum += nums[right] # Expand: add new element
8
9 # Shrink: while sum >= target, try to minimize
10 while window_sum >= target:
11 best = min(best, right - left + 1) # Record valid window
12 window_sum -= nums[left] # Remove leftmost
13 left += 1 # Shrink
14
15 return best if best != float('inf') else 0
16# O(n) time, O(1) space

Dry Run: nums = [2, 3, 1, 2, 4, 3], target = 7

rightaddsumleftactionbest
0+2202 < 7, expandinf
1+3505 < 7, expandinf
2+1606 < 7, expandinf
3+2808 ≥ 7, record 4, shrink4
3-2616 < 7, stop shrink4
4+410110 ≥ 7, record 4, shrink4
4-3727 ≥ 7, record 3, shrink3
4-1636 < 7, stop3
5+3939 ≥ 7, record 3, shrink to [4,3] sum=7, record 22

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).

Time: O(n)Space: O(1)

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 if instead of while for variable-size windows. You might need to shrink multiple times in a single step. Always use while for 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) or map.size gives 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], not nums[right - k + 1].

Pro Tip: Start Brute Force, Then Optimize

In your interview, start by explaining the O(n*k) brute force: “For each starting index, sum the next k elements.” Then say: “But notice that consecutive windows share k-1 elements. I can update the sum incrementally.” This shows the interviewer your thought process, not just the answer.

Pro Tip: Verbalize the Window State

Before coding, state out loud: “My window state is [sum / frequency map / count of valid chars]. When I expand right, I update state by [adding X]. When I shrink left, I update state by [removing Y].” This prevents bugs and impresses interviewers.

Pro Tip: Time Complexity Proof

“Why is this O(n) and not O(n squared)?” The key insight: each element is added to the window at most once and removed at most once. The inner while loop does not run n times per outer iteration — across all iterations of the outer loop, left moves at most n times total. So total work = O(2n) = O(n).

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.

1

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).

2

Fixed or Variable?

Is the window size given? Fixed → Template 1. Are you finding the optimal window size? Variable → Template 2/3.

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.

4

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.

5

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

ProblemBrute ForceSliding WindowTemplate
Max Sum Subarray of Size KO(n*k)O(n)1 (Fixed)
Longest Substring No RepeatO(n³)O(n)3 (Map)
Minimum Window SubstringO(n²*|t|)O(n+|t|)2+3
Permutation in StringO(n*k*26)O(n)1+3
Fruit Into BasketsO(n²)O(n)3 (Map)
Min Size Subarray SumO(n²)O(n)2 (Variable)

Related Patterns