Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Data Structures & Algorithms (DSA)  Module 2 – Arrays, strings and in-place techniques

Sliding windows: fixed and variable size

Keep a running summary of a stretch as one item enters and one leaves: fixed windows, variable windows with their invariant, and the inputs that break them.

  • Intermediate
  • 25 minutes
  • Examples run with Python 3.14.8, Pyodide 314.0.7, Node.js 24.21.0, quickjs 0.32.0, Java 25, Clang 21.0.0 and Go 1.27.1
  • By MySmartCoPilot
Free core, Pro depth: 9 of 25 minutes free
Free: Fixed windows: one item in, one item out · Variable windows: grow on the right, shrink on the left · When a window is the wrong tool · Exercise: the shortest run that reaches a target. With a pass: 3 in-depth parts and 3 interview answers, about 16 minutes.

What you will learn

  • Maintain a fixed-size window sum in O(1) per step
  • Apply a variable window that grows and shrinks while keeping an invariant
  • Recognise when sliding windows fail and what to use instead

Before you start

A sliding window is a stretch of consecutive items, from index left to index right, that moves along an array while a summary of what it holds, such as its total, is kept up to date. Instead of adding up every stretch from scratch, the window adds the item that enters on the right and removes the item that leaves on the left, so each step costs O(1). A fixed window always holds k items and answers questions such as the highest seven-day average. A variable window takes in items on the right and drops them from the left while a rule is broken, and answers questions such as the longest stretch within a budget. Both turn scans that cost O(n·k) or O(n²) into O(n). This lesson builds both in Python, traces them on small inputs and names the inputs on which a window gives wrong answers.

Fixed windows: one item in, one item out

A fixed window of k readings ends at right and holds a[right − k + 1] to a[right]. Build the first total with k additions; after that, each step adds the reading that enters and subtracts the one that leaves, total += a[right] − a[right − k]. Over n readings that is k + 2(n − k) additions, against (n − k + 1) × k for adding every window from scratch, so the cost of a step no longer depends on k. The program finds the highest seven-day average in 60 days of synthetic air-quality readings both ways:

The highest 7-day average, by brute force and with a fixed window Python · fixed/aqi_week.py
"""The highest 7-day average of 60 days of air-quality readings (synthetic), by brute force and with a fixed window."""

import random

rng = random.Random(42)
aqi = [max(20, round(110 + 60 * rng.uniform(-1, 1) + (40 if 30 <= d < 40 else 0))) for d in range(60)]
K = 7
print("first 14 days:", aqi[:14])


def brute(a, k):
    """Adds up every window of k days from scratch."""
    best, adds = None, 0
    for start in range(len(a) - k + 1):
        total = 0
        for d in range(start, start + k):
            total += a[d]
            adds += 1
        if best is None or total > best[0]:
            best = (total, start)
    return best, adds


def window(a, k, trace=0):
    """Keeps one total: each step adds the day that enters and subtracts the day that leaves."""
    total = sum(a[:k])
    adds = k
    best = (total, 0)
    for right in range(k, len(a)):
        total += a[right] - a[right - k]  # day `right` enters, day `right - k` leaves
        adds += 2
        if total > best[0]:
            best = (total, right - k + 1)
        if right < k + trace:
            print(f"  day {right:>2} enters (+{a[right]}), day {right - k:>2} leaves (-{a[right - k]}): total {total}")
    return best, adds


(b_total, b_start), b_adds = brute(aqi, K)
print("the first steps of the window:")
(w_total, w_start), w_adds = window(aqi, K, trace=3)
print(f"brute force: best total {b_total} from day {b_start}, {b_adds} additions")
print(f"window:      best total {w_total} from day {w_start}, {w_adds} additions")
print(f"highest 7-day average: {w_total / K:.1f}, days {w_start} to {w_start + K - 1}")

Output

first 14 days: [127, 53, 83, 77, 138, 131, 157, 60, 101, 54, 76, 111, 53, 74]
the first steps of the window:
  day  7 enters (+60), day  0 leaves (-127): total 699
  day  8 enters (+101), day  1 leaves (-53): total 747
  day  9 enters (+54), day  2 leaves (-83): total 718
brute force: best total 1207 from day 30, 378 additions
window:      best total 1207 from day 30, 113 additions
highest 7-day average: 172.4, days 30 to 36

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 aqi_week.py

Both find days 30 to 36, with an average of 172.4, but the window needed 113 additions where the brute force needed 378. The gap grows with k: for a 30-day window over a year of daily readings, the brute force makes 10,080 additions and the window 700.

A fixed window of 3 moving one place, 10 − 2 + 4 = 12, and a variable window holding 0, 6 and 2 within a budget of 8.fixed window, k = 3: one in, one outbefore: 2 + 7 + 1 = 105021721344653687after: 10 − 2 + 4 = 122 leaves on the left, 4 enters on the rightvariable window, total at most 8at right = 4 the total hit 9, so the 1 left4106230192leftright0 + 6 + 2 = 8right moves every step; left only while over 8

One step of a fixed window and the state of a variable window

Text description of the diagram

The top half shows the values 5, 2, 7, 1, 4, 6, 3 and 8 at positions 0 to 7. A dashed bracket over positions 1 to 3 marks the window before the step, with the total 2 + 7 + 1 = 10. Positions 2 to 4 are shaded and a solid bracket under them marks the window after the step: 2 leaves on the left, 4 enters on the right, and the new total is 10 − 2 + 4 = 12.

The bottom half shows a delivery log with the delays 4, 1, 0, 6, 2, 3, 0, 1, 9 and 2 and a budget of 8. Arrows mark left at position 2 and right at position 4, and the shaded window holds 0 + 6 + 2 = 8. The first two delays are greyed out because they have left the window: when the delay at position 4 arrived, the total reached 9, so left moved past the 1. A note says that right moves every step while left moves only while the total is over the budget.

Line Graph Maker Plot daily readings next to their 7-day average to see what a fixed window smooths out.

Variable windows: grow on the right, shrink on the left

A courier firm logs the delay of every delivery in minutes, never negative, and wants the longest run of consecutive deliveries whose delays add up to at most a budget B. A variable window answers it in one pass:

  1. Move right on by one and add its delay to total.
  2. While total > B, subtract delays[left] and move left on.
  3. The run from left to right is now the longest run within budget that ends at right; compare its length with the best so far.

Step 3 relies on a monotone rule. Delays are never negative, so a run within budget stays within budget when it loses deliveries at either end, and the deliveries left of left can be dropped for good. Here is the trace with a budget of 8:

The longest run within a budget, traced, then checked on 2,000 logs Python · variable/delays.py
"""The longest run of consecutive deliveries whose delays add up to at most a budget, with a variable window."""

import random


def longest_within(delays, budget, trace=False):
    """(length, first) of the longest run with total delay <= budget; delays are never negative."""
    left, total = 0, 0
    best = (0, 0)
    for right, d in enumerate(delays):
        total += d  # the delivery at `right` enters
        while total > budget:  # over budget: drop deliveries from the left until the run fits again
            total -= delays[left]
            left += 1
        if right - left + 1 > best[0]:
            best = (right - left + 1, left)
        if trace:
            print(f"  right {right}: +{d:<2} window {left}..{right}  total {total:>2}  best {best}")
    return best


def brute(delays, budget):
    best = (0, 0)
    for i in range(len(delays)):
        total = 0
        for j in range(i, len(delays)):
            total += delays[j]
            if total <= budget and j - i + 1 > best[0]:
                best = (j - i + 1, i)
    return best


log = [4, 1, 0, 6, 2, 3, 0, 1, 9, 2]
print(f"delays {log}, budget 8:")
print("result:", longest_within(log, 8, trace=True))

rng = random.Random(1)
cases = [([rng.randint(0, 9) for _ in range(rng.randint(0, 20))], rng.randint(0, 20)) for _ in range(2000)]
print("matches the brute force on 2,000 random logs:", all(longest_within(d, b) == brute(d, b) for d, b in cases))

Output

delays [4, 1, 0, 6, 2, 3, 0, 1, 9, 2], budget 8:
  right 0: +4  window 0..0  total  4  best (1, 0)
  right 1: +1  window 0..1  total  5  best (2, 0)
  right 2: +0  window 0..2  total  5  best (3, 0)
  right 3: +6  window 1..3  total  7  best (3, 0)
  right 4: +2  window 2..4  total  8  best (3, 0)
  right 5: +3  window 4..5  total  5  best (3, 0)
  right 6: +0  window 4..6  total  5  best (3, 0)
  right 7: +1  window 4..7  total  6  best (4, 4)
  right 8: +9  window 9..8  total  0  best (4, 4)
  right 9: +2  window 9..9  total  2  best (4, 4)
result: (4, 4)
matches the brute force on 2,000 random logs: True

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 delays.py

At right = 3 the total reaches 11, so the 4 leaves. At right = 8 a single delay of 9 is over the budget on its own, so the window empties until the next delivery. The answer is a run of 4 deliveries starting at position 4. The while loop inside the for loop does not make the scan quadratic: right moves n times, and left moves forward at most n times in total and never back, so the whole scan is at most 2n moves, O(n).

When a window is the wrong tool

Shrinking is safe only when a shorter window keeps a valid window valid. These inputs need something else:

  • Negative values. A total can rise again after it falls, so dropping items on the left can lose the answer.
  • Exact totals, any signs. Prefix sums with a hash map count the stretches whose total equals a target in one pass.
  • The shortest stretch reaching a target, any signs. Prefix sums with a monotonic deque find it in O(n).
  • Distinct values. A window over letters or labels keeps a dictionary of counts and deletes a key at count 0.
Fixed window Variable window
Fixed part The number of items, k The rule every window must keep
Each step One item in, one item out One item in, then items out until the rule holds
Use The best total or average of k items The longest or shortest stretch that keeps a rule
Below zero Still correct Wrong for rules about totals
Moves 2n − k in all At most 2n in all

Timed drills on many window problems are in the sliding window patterns of Coding Interview Patterns.

Exercise: the shortest run that reaches a target

Exercise · Medium · Python, JavaScript

The shortest run of shops that reaches a footfall target

A market street has n shops in a row, and footfall[i] is the number of visitors shop i had yesterday, never negative. A sponsor wants the shortest run of consecutive shops whose visitors add up to at least target.

Write shortest_run(footfall, target) (JavaScript: shortestRun(footfall, target)) that returns the length of that run, or 0 if no run reaches the target. target is at least 1.

  • For [120, 40, 300, 80, 0, 90, 260] and a target of 350, the answer is 2: shops 5 and 6 have 90 + 260 visitors.
  • For [50, 50] and a target of 101, the answer is 0.

Use a variable window: grow it on the right, and while it reaches the target, record its length and shrink it from the left. The sample tests check small streets, compare your function with a brute force on 300 random streets, and end with 200,000 shops, which a quadratic solution cannot finish in time.

Python · Starter code · footfall.py

def shortest_run(footfall, target):
    """Length of the shortest run of consecutive shops with total footfall >= target, or 0 if there is none."""
    # Replace this line with your code.
    return 0
The sample tests · test_footfall.py
import random

from footfall import shortest_run


def brute(footfall, target):
    best = 0
    for i in range(len(footfall)):
        total = 0
        for j in range(i, len(footfall)):
            total += footfall[j]
            if total >= target:
                if best == 0 or j - i + 1 < best:
                    best = j - i + 1
                break
    return best


def test_examples():
    """the two streets of the prompt"""
    assert shortest_run([120, 40, 300, 80, 0, 90, 260], 350) == 2
    assert shortest_run([50, 50], 101) == 0


def test_small_streets():
    """an empty street, one shop that is enough, one that is not, and a target met only by the whole street"""
    assert shortest_run([], 5) == 0
    assert shortest_run([500], 500) == 1
    assert shortest_run([499], 500) == 0
    assert shortest_run([1, 2, 3, 4], 10) == 4
    assert shortest_run([0, 0, 7, 0, 0], 7) == 1


def test_random_streets():
    """300 random streets with zeros, against the brute force"""
    rng = random.Random(21)
    for _ in range(300):
        footfall = [rng.choice([0, 0, 1, 2, 5, 9]) for _ in range(rng.randint(0, 30))]
        target = rng.randint(1, 40)
        assert shortest_run(footfall, target) == brute(footfall, target), (footfall, target)


def test_long_street():
    """200,000 shops, which only a linear solution handles in time"""
    footfall = [k % 10 for k in range(200_000)]
    assert shortest_run(footfall, 45) == 9
    assert shortest_run(footfall, 10 ** 9) == 0

JavaScript · Starter code · footfall.mjs

/** Length of the shortest run of consecutive shops with total footfall >= target, or 0 if there is none. */
export function shortestRun(footfall, target) {
  // Replace this line with your code.
  return 0;
}
The sample tests · footfall.test.mjs
import { test, assert } from 'mysmartcopilot:test';
import { shortestRun } from './footfall.mjs';

function brute(footfall, target) {
  let best = 0;
  for (let i = 0; i < footfall.length; i++) {
    let total = 0;
    for (let j = i; j < footfall.length; j++) {
      total += footfall[j];
      if (total >= target) {
        if (best === 0 || j - i + 1 < best) best = j - i + 1;
        break;
      }
    }
  }
  return best;
}

test('the two streets of the prompt', () => {
  assert.equal(shortestRun([120, 40, 300, 80, 0, 90, 260], 350), 2);
  assert.equal(shortestRun([50, 50], 101), 0);
});

test('an empty street, one shop that is enough, one that is not, and a target met only by the whole street', () => {
  assert.equal(shortestRun([], 5), 0);
  assert.equal(shortestRun([500], 500), 1);
  assert.equal(shortestRun([499], 500), 0);
  assert.equal(shortestRun([1, 2, 3, 4], 10), 4);
  assert.equal(shortestRun([0, 0, 7, 0, 0], 7), 1);
});

test('300 random streets with zeros, against the brute force', () => {
  let seed = 21;
  const random = () => (seed = (seed * 48271) % 2147483647) / 2147483647;
  const choices = [0, 0, 1, 2, 5, 9];
  for (let k = 0; k < 300; k++) {
    const footfall = Array.from({ length: Math.floor(random() * 31) }, () => choices[Math.floor(random() * choices.length)]);
    const target = 1 + Math.floor(random() * 40);
    assert.equal(shortestRun(footfall, target), brute(footfall, target), JSON.stringify([footfall, target]));
  }
});

test('200,000 shops, which only a linear solution handles in time', () => {
  const footfall = Array.from({ length: 200000 }, (_, k) => k % 10);
  assert.equal(shortestRun(footfall, 45), 9);
  assert.equal(shortestRun(footfall, 10 ** 9), 0);
});
A hint

Keep left and a running total. For each right, add footfall[right]; then, while total >= target, the window from left to right reaches the target, so update the answer with its length and remove footfall[left] before moving left on. Every shop enters and leaves once.

The sample tests run on this device, in your browser (Pyodide, QuickJS): nothing is sent to mysmartcopilot.com. The first run of each language downloads it: Python (about 13.5 MB) or JavaScript (about 0.6 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.

Check yourself

4 questions about this lesson. Every answer and why it is right is on the page, behind “Show the answer”. Your score stays in this browser.

  1. Question 1 of 4 A fixed window of 30 readings slides over 365 daily readings. How many additions and subtractions does it make in all, counting the 30 additions that build the first window?

    Type a number.

    Show the answer to question 1

    Answer: 700

    The first window takes 30 additions. Each of the other 335 windows adds one reading and subtracts one, 2 × 335 = 670, so the total is 700. Adding every window from scratch would take 336 × 30 = 10,080 additions.

  2. Question 2 of 4 A variable window has a while loop inside its for loop. Why is the scan still O(n)?

    Choose one answer.

    Show the answer to question 2

    Answer: left only ever moves forward, so all the while loop's passes together move it at most n times

    One step of right can make the while loop run many times, but each pass moves left one place forward, and left never moves back or past right + 1. Over the whole scan that is at most n passes, so at most 2n moves in all.

  3. Question 3 of 4 The window for "the longest run with total at most 1" gives a wrong answer on which input?

    Choose one answer.

    Show the answer to question 3

    Answer: [3, −3, 1], where the whole input totals 1

    The total 3 is over the budget, so the window drops the 3 before the −3 arrives, and it reports 2 instead of 3. With a negative value, a run within budget can contain a part that is not, so dropping items on the left can lose the answer. [9] correctly gives 0 and [0, 1, 0] correctly gives 3.

  4. Question 4 of 4 What is the shortest run of [3, 1, 4, 1, 5] whose total reaches 6?

    Choose one answer.

    Show the answer to question 4

    Answer: 2, the run 1 + 5

    No single value reaches 6, and 1 + 5 = 6 at the end does with two values. The window finds it when right reaches the 5: the window 4, 1, 5 totals 10, and shrinking it to 1, 5 still reaches 6.

Why the variable window is right and O(n), in five languages Pro: opens with a Pro, Premium or Ultimate pass

The variable window keeps one invariant, and two short arguments show that it never misses the answer and never makes more than 2n moves. This part gives both, counts the moves on four logs and runs the window in five languages.

In this part:

  • The invariant, and why left never moves back
  • The moves of both indices, counted
  • The window in Python, JavaScript, Java, C++ and Go

3 runnable examples (C++, Go, Java, JavaScript, Python) · about 3 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Windows over letters and labels: counts, a slip and the cost of hashing Pro: opens with a Pro, Premium or Ultimate pass

When the rule is about which values a window holds, such as deliveries to at most k different zones, the summary becomes a table of counts. This part builds it with a dictionary and with 26 counts, reproduces a slip that shrinks windows too far, and times both tables on a million deliveries.

In this part:

  • A dictionary of counts, deleted at zero
  • The slip that keeps zero counts, found by every short route
  • 26 counts against a dictionary, measured

1 runnable example (Python) · about 3 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

When windows fail: negative values reproduced, and two prefix-sum fixes Pro: opens with a Pro, Premium or Ultimate pass

A negative value lets a total rise again after it falls, and the variable window then gives wrong answers. This part finds the smallest such input automatically, then replaces the window with prefix sums and a hash map for exact totals, and with a monotonic deque for the shortest stretch that reaches a target.

In this part:

  • The smallest counterexample, found by brute force
  • Exact totals with prefix sums and a hash map
  • The shortest stretch reaching a target, with a monotonic deque
  • Choosing the tool

1 runnable example (Python) · about 3 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Interview questions

  1. Fresher to mid · Warm-up

    Why does a fixed window of k items over n items take O(n) time in total, whatever k is, when adding up every window from scratch takes O(n·k)?

    Neighbouring windows share k − 1 items. Moving the window one place drops the item that leaves on the left and adds the item that enters on the right, so the total changes with two operations, not k. Building the first window costs k additions and each of the other n − k windows costs 2, which is k + 2(n − k) operations, at most 2n, so O(n) for any k. Adding every window from scratch repeats the shared items: (n − k + 1) × k additions, worst near k = n / 2 at about n² / 4. The update works for any summary that changes by one item at a time, such as a total, a count or a table of counts, but not for a maximum: when the largest item leaves, the old summary cannot tell what the new maximum is.

  2. Mid to senior

    A sensor reports one reading a second, and a calibration offset makes some readings negative. Find the longest run of consecutive seconds whose average is at most a threshold T, for up to 10⁶ readings. Why does a plain variable window fail here, and what do you use instead?

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  3. Fresher to mid

    Find the shortest run of consecutive shops whose footfall reaches a target, where every footfall is 0 or more. Prove that the variable window never skips the answer, and say exactly what breaks if a shop may report a negative correction.

    Model answer, Pro. First step and follow-ups

  4. Mid to senior

    Your function for the longest run with at most k distinct labels passes every test but times out on a log of 10⁶ labels. List the likely causes, how you would find the real one, and the fix for each.

    Model answer, Pro. First step and follow-ups

Key takeaways

  • A sliding window keeps a summary of a stretch up to date as items enter on the right and leave on the left.
  • A fixed window of k items adds one item and subtracts one per step, k + 2(n − k) additions in all.
  • A variable window grows on the right and shrinks from the left while a rule is broken; left never moves back, so the scan is O(n).
  • The variable window needs a monotone rule: with negative values, rules about totals call for prefix sums instead.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.