Coding Interview Patterns

Course Content

Coding Interview Patterns

20 sections · 146 lessons

Sliding Windows: The Core Idea


A sliding window keeps a running summary of one contiguous stretch of the input: a sum, a set of characters, a count of each letter. When the stretch moves by one position, you repair the summary by one element instead of rebuilding it from scratch.

That one change is the difference between checking every subarray, which is O(n²) or O(n³), and a single O(n) pass. Almost every "longest substring", "shortest subarray" or "every window of size k" question in an interview is this pattern.

What the brute force throws away142102310123456leavesentersThe next window shares 4, 2, 10 with this one — the brute force adds those three again.
Consecutive windows overlap almost entirely, so the summary should be repaired, never rebuilt.

The intuition: a moving seven-day total

A shop wants its sales total for every seven-day stretch of the year. The slow way adds up seven days for each stretch: 359 stretches × 7 additions. The quick way notices that Monday-to-Sunday and Tuesday-to-Monday share six days. To move the stretch forward one day, add the new day and subtract the day that fell off the back. That is two operations per step, however long the stretch is.

Now suppose the question changes to "the longest run of days where total sales stayed under ₹50,000". The stretch is no longer a fixed size. You grow it at the front while it stays under the limit, and when it goes over, you drop days from the back until it is under again. The front and back both only move forward. That is a variable-size window.

How to recognise it

  • "Subarray" or "substring". Both mean contiguous. This is the most reliable signal. "Subsequence" and "subset" allow gaps, and those are dynamic programming or backtracking problems, not windows.
  • A longest, shortest or count question about those stretches. "The longest substring with at most two distinct characters." "The shortest subarray whose sum is at least 7." "How many subarrays contain exactly k different values."
  • A fixed size is named. "Every window of length k", "the best average of k consecutive days". This is the easy form.
  • The brute force is "check every subarray". There are n(n + 1)/2 of them. With n up to 10⁵, that is 5 × 10⁹ subarrays, far too many. An O(n) or O(n log n) answer is expected, and a window is the usual one.

Before committing, check the values. A window on sums needs non-negative numbers. If the constraints say values can be negative, a sum-based window is wrong, and you need prefix sums (or Kadane's algorithm for a maximum sum). Character-count windows have no such problem.

How it works: two boundaries and one summary

Keep two indices, start and end, and a summary that always describes exactly the elements from start to end. The loop has three steps:

  1. Expand — move end right by one and add the entering element to the summary.
  2. Shrink — while the window breaks the rule, remove the element at start from the summary and move start right.
  3. Record — the window now obeys the rule, so update the answer from it.

A full trace makes it concrete. Problem: the longest substring with no repeated character. Input: "abba". Summary: the set of characters in the window. The rule breaks when the entering character is already in the set.

endcharSet beforeActionstartWindowBest
0a{}add a0a (1)1
1b{a}add b0ab (2)2
2b{a, b}b is inside: remove a, start = 112
2b{b}b still inside: remove b, start = 222
2b{}add b2b (1)2
3a{b}add a2ba (2)2

The answer is 2. Look at end = 2: the shrink ran twice before the window was legal again. That is why the shrink is a while, never an if.

a0b1b2a3end=0startend{a}best = 1expanda0b1b2a3end=1startend{a, b}best = 2expanda0b1b2a3end=2startend{a, b}best = 2duplicate 'b' cannot enter — remove 'a', start → 1a0b1b2a3end=2startend{b}best = 2SHRINK RAN TWICE — this is why the shrink is a while, not an ifa0b1b2a3end=2startend{b}best = 2valid againa0b1b2a3end=3startend{b, a}best = 2expand; answer stays 2both boundaries only ever move RIGHT — each index is visited at most twice, so the whole scan is O(n)
Frames 3 and 4 are the same value of end: the shrink runs twice, which is why it has to be a while loop.

Why it is correct, and why it is O(n)

Correct. Moving start right is safe because of one property: if the window from start to end breaks the rule, then every longer window that contains it also breaks the rule. If "abb" has a repeat, so does "abba". So once a start position fails for some end, it can never be part of a better answer later, and it never needs to be revisited. This "breaking the rule only gets worse as the window grows" property is what every window problem needs. It is exactly what negative numbers destroy in a sum.

O(n). The inner while looks as if it could make the loop O(n²). It cannot. start never moves left, so across the whole run it moves right at most n times in total. end moves right exactly n times. That is at most 2n moves, each doing O(1) work on the summary. This way of counting, adding up the total work over the whole run instead of bounding each step, is called amortised analysis. It appears again with monotonic stacks, where each element is pushed once and popped once.

Variants: the shapes a window takes

ShapeSizeShrink conditionWhen to recordExample
Fixedalways knone: one in, one outafter each slideMaximum Sum Subarray of Size K
Longestgrows and shrinkswhile the window is invalidafter the shrink loopLongest Substring Without Repeating Characters
Shortestgrows and shrinkswhile the window is still validinside the shrink loopMinimum Window Substring
Countinggrows and shrinkswhile invalidadd end − start + 1 each stepSubarrays with K Different Integers

The summary is either numeric (a running sum, a count of zeros) or structural (a set, or a map of counts). A count map is what you need when the window's contents matter, as in Permutation in String.

The templates

Fixed size. Build the first window once, then slide.

Python
def max_sum_of_k(nums: list[int], k: int) -> int:    """Largest sum of any k consecutive values. Assumes 1 <= k <= len(nums)."""    window_sum = sum(nums[:k])                    # first window: O(k), once    best = window_sum    for end in range(k, len(nums)):        window_sum += nums[end] - nums[end - k]   # one enters, one leaves        best = max(best, window_sum)    return best

When end is the entering index, the window covers end − k + 1 to end, so the element that just left is at end − k. Derive that each time rather than memorising it.

Longest valid window. Here "valid" means "at most k distinct characters", but only the while condition changes from problem to problem.

Python
def longest_with_at_most_k_distinct(text: str, k: int) -> int:    """Length of the longest substring with at most k distinct characters."""    counts: dict[str, int] = {}    start = 0    best = 0    for end, char in enumerate(text):        counts[char] = counts.get(char, 0) + 1     # 1. expand        while len(counts) > k:                     # 2. shrink while INVALID            leaving = text[start]            counts[leaving] -= 1            if counts[leaving] == 0:                del counts[leaving]                # or len(counts) over-counts            start += 1        best = max(best, end - start + 1)          # 3. record AFTER the loop    return best

Line by line: the entering character is counted; while there are too many distinct characters, the character at start is removed (and its key deleted when it reaches zero, so len(counts) stays honest) and start moves on; the window is now valid, so its length is a candidate.

Shortest valid window. Flip the loop: shrink while the window is still valid, recording inside, to find the smallest window that works.

Python
def min_subarray_len(target: int, nums: list[int]) -> int:    """Length of the shortest subarray with sum >= target (positive numbers), or 0."""    start = 0    window_sum = 0    best = float("inf")    for end, value in enumerate(nums):        window_sum += value        while window_sum >= target:          # still valid: record, then try smaller            best = min(best, end - start + 1)            window_sum -= nums[start]            start += 1    return 0 if best == float("inf") else best

On target = 7, nums = [2, 3, 1, 2, 4, 3], the sum first reaches 8 at end = 3 (window [2, 3, 1, 2], length 4). At end = 4 it shrinks through lengths 4 and 3. At end = 5 it shrinks through [2, 4, 3] (length 3) to [4, 3] (sum 7, length 2). The answer is 2.

Grow, then shrink until legal againMoveright, add itIs thewindow illegal?While so,shrink leftRecordthe answerA while loop, not an if — one removal is rarely enough to restore validity.
Two questions define every variable window: what makes it illegal, and when is the answer read.

Complexity

All three templates run in O(n) time: each index enters once and leaves at most once. The fixed window also spends O(k) building the first window, which is within O(n). Space is O(1) for a numeric summary, and O(d) for a set or count map, where d is the number of distinct values the window can hold. For lowercase letters d ≤ 26, so that is O(1) too.

Where it goes wrong

  1. if instead of while in the shrink. It removes one element when several may be needed. On "abba" the longest-unique-substring code then returns 3 instead of 2. On "abcabcbb" it still returns 3, the right answer, so the bug hides on the example most people test first.
  2. Recording at the wrong moment. In a longest problem, record after the shrink loop, when the window is valid. In a shortest problem, record inside it, before each removal. Mixing them measures windows that are invalid or already broken.
  3. Not cleaning up the leaving element. Decrementing a count without deleting the zero key makes len(counts) over-report the distinct count, so the window shrinks too far. Removing the entering character instead of text[start], or moving start without touching the summary, leaves the summary describing a window that no longer exists.
  4. Using a window where none applies. A sum with negative numbers breaks the "only gets worse as it grows" property. A "subsequence" is not contiguous. Both need a different pattern.
Four window bugs, four symptomsWindow bugsif instead of whileAnswer read too earlyLeaver not cleaned upNo window applies
Each fails differently: too large, off by one, slowly corrupted, or plausible and wrong.

Check your understanding

0 of 3 answered

1.The problem asks for the shortest subarray with sum at least k, and values can be negative. Why does a sliding window fail?

2.Your longest-unique-substring code returns 3 on "abcabcbb" (correct) but 3 on "abba" (should be 2). What is the most likely bug?

3.Why is the variable-size window O(n) even though it has a while inside a for?