Skip to content
Insight CodeAcademy

The Sliding Window Pattern

In this article we will learn what the sliding window pattern is, how it turns an O(n²) brute force into an O(n) scan, the difference between fixed-size and variable-size windows, how to recognise a sliding window problem from its wording, and the mistakes that quietly break the invariant.

Naveen Garg7 min read
The Sliding Window Pattern

Most array and string problems have an obvious brute force: try every subarray. That is O(n²) subarrays, and often O(n) work to evaluate each, giving O(n³). The sliding window pattern collapses that to a single O(n) pass by noticing that consecutive subarrays overlap almost entirely.

In this article we will learn what the sliding window pattern is, how it eliminates redundant work, the difference between fixed-size and variable-size windows, how to recognise a sliding window problem from its wording, and the mistakes that quietly break the invariant.

What is the sliding window pattern?

Short answer

The sliding window pattern maintains a contiguous range of a sequence, defined by a left and a right index, and updates a running result as the range moves. Because each element enters the window once and leaves once, the total work is O(n) instead of the O(n²) cost of re-examining every candidate range from scratch.

The insight is that the window from index 1 to 4 shares three of its four elements with the window from 0 to 3. Recomputing the sum of all four throws that overlap away. Adding the incoming element and subtracting the outgoing one keeps it.

Why is the brute force so wasteful?

Take: find the maximum sum of any subarray of size k.

def max_sum_bruteforce(nums, k):
    best = float("-inf")
    for i in range(len(nums) - k + 1):
        window_sum = sum(nums[i:i + k])   # O(k) work, repeated every iteration
        best = max(best, window_sum)
    return best

This is O(n × k). Each sum() call re-adds k - 1 numbers that were already added by the previous iteration.

Amber cells are numbers the previous window already added. The sliding window keeps that sum instead of rebuilding it.

How does a fixed-size window fix it?

Slide the window one position at a time, adjusting the running total:

def max_sum_sliding(nums, k):
    if len(nums) < k:
        return None
 
    window_sum = sum(nums[:k])   # build the first window once
    best = window_sum
 
    for right in range(k, len(nums)):
        window_sum += nums[right]        # element enters on the right
        window_sum -= nums[right - k]    # element leaves on the left
        best = max(best, window_sum)
 
    return best

O(n) time, O(1) space. Every element is added exactly once and subtracted exactly once.

Max sum of a size-3 window. One addition and one subtraction per step, instead of re-adding the whole window.

The shape generalises:

Build the first window
For each new right index:
    Add the incoming element
    Remove the outgoing element
    Update the answer

What about variable-size windows?

When the window size is not given but implied by a condition — "the longest substring with at most k distinct characters" — the right pointer expands the window and the left pointer contracts it whenever the condition breaks.

def longest_substring_k_distinct(s, k):
    counts = {}
    left = 0
    best = 0
 
    for right, char in enumerate(s):
        counts[char] = counts.get(char, 0) + 1
 
        # Shrink from the left until the window is valid again
        while len(counts) > k:
            left_char = s[left]
            counts[left_char] -= 1
            if counts[left_char] == 0:
                del counts[left_char]
            left += 1
 
        best = max(best, right - left + 1)
 
    return best

The inner while loop looks like it makes this quadratic. It does not. left only ever moves forward, and it can move at most n times in total across the entire run. Both pointers traverse the array once, so the whole function is O(n).

The right edge always grows; the left edge only moves when the window breaks the rule.

This amortised argument is the part interviewers probe. Be ready to explain why a nested loop is still linear: the inner loop's total iterations across the whole algorithm are bounded by n, not by n per outer iteration.

Fixed vs variable windows

Fixed sizeVariable size
Window lengthGiven as kDetermined by a condition
Left pointer movesEvery iterationOnly when the window is invalid
Typical question"…of size k""longest / shortest / smallest …such that"
Inner loopNonewhile that restores validity
TimeO(n)O(n) amortised
ExampleMax sum of size-k subarrayLongest substring without repeats

How do you recognise one?

The wording gives it away. Look for all three:

  1. The problem concerns a contiguous subarray or substring. If reordering or skipping elements is allowed, this is not a window problem.
  2. It asks for a longest, shortest, maximum, minimum or count.
  3. There is a condition the window must satisfy — a size, a sum, a distinct-character limit.
Check all three before reaching for the pattern.

If the array contains negative numbers and the condition involves a sum threshold, be careful: the sum is no longer monotonic as the window grows, so the standard shrink rule breaks. Prefix sums with a hash map is usually the right pattern there instead.

Common mistakes

  • Updating the answer before restoring validity. In a variable window, measure right - left + 1 only after the while loop has finished shrinking.
  • Off-by-one in the outgoing index. For a fixed window ending at right, the element leaving is right - k, not right - k + 1.
  • Forgetting to delete zero counts. If you track distinct characters with a hash map, a key with count 0 still counts toward len(counts) until you remove it.
  • Using a window on non-contiguous problems. "Any subsequence" is not a window problem.
  • Assuming shrinking always helps with negative numbers. Removing a negative element increases the sum.

Conclusion

The sliding window pattern turns repeated evaluation of overlapping ranges into a single pass by adding the element that enters and removing the element that leaves. Fixed-size windows move both pointers in lockstep; variable-size windows expand on the right and contract on the left only when a condition is violated.

The pattern applies whenever a problem asks for an optimal contiguous segment subject to a constraint, and it reliably converts an O(n²) brute force into O(n).

If the complexity argument in the variable-size section was not obvious, time and space complexity explained covers the amortised reasoning it depends on.

Frequently asked questions

When should I use a sliding window instead of two pointers?
Sliding window is a special case of two pointers where both pointers move in the same direction and the region between them is meaningful as a unit. Use it when the problem asks about contiguous subarrays or substrings. Use general two pointers when the pointers move toward each other, as in a sorted-array pair sum.
Does the sliding window work on unsorted arrays?
Yes for problems about contiguous segments, such as the maximum sum of a fixed-size window. It does not work when the problem requires the window sum to behave monotonically and the array contains negative numbers, because shrinking the window can then increase the sum.

Read next

DSA6 min read

BFS vs DFS: Which Traversal Should You Use?

In this article we will learn how breadth-first search and depth-first search actually differ, why BFS finds the shortest path in an unweighted graph and DFS does not, how their memory usage compares on wide and deep graphs, and a decision rule for choosing between them in an interview.

DSA8 min read

Time and Space Complexity Explained

In this article we will learn what time and space complexity mean, why Big-O notation drops constants, how to analyse loops and recursion step by step, what auxiliary space is, and how to compare two solutions without ever running them.

System Design7 min read

SQL vs NoSQL: How to Actually Choose

In this article we will learn what genuinely separates SQL and NoSQL databases, why the common answers about scale and schema are mostly wrong, the four NoSQL families and what each is for, and a decision procedure you can defend in a system design interview.