Part 4 · Lesson 22 · 20 min read

Sliding Window: Fixed Size

Reuse the last window's work instead of recomputing it.

On this page

  1. Every block of k items
  2. The brute force repeats work
  3. Slide: add one, remove one
  4. Traced: largest sum of 3 neighbours
  5. The fixed-window template
  6. Averages of every window
  7. Counting inside a window
  8. A window with a frequency map
  9. Practice questions (6)
  10. Make it stick
  11. What's next

Every block of k items

Many problems ask about every window. A window is a block of k items that sit next to each other in an array or a string. Examples: the largest sum of 3 days in a row, the average of every 5 readings, or the most vowels in any 4 letters in a row. An array of n items has n − k + 1 such windows.

nums =
2
0
1
1
start
5
2
1
3
end
3
4
2
5
↑ index (position), always starting at 0
One window of size k = 3 (indices 2..4). The next window drops index 2 and adds index 5.

The brute force repeats work

Computing each window from scratch costs k steps per window:

JavaScript
function maxSumBrute(nums, k) {  let best = -Infinity;  for (let start = 0; start + k <= nums.length; start++) {    let sum = 0;    for (let i = start; i < start + k; i++) sum += nums[i];   // k steps, every time    best = Math.max(best, sum);  }  return best;}console.log(maxSumBrute([2, 1, 5, 1, 3, 2], 3)); // 9

That costs O(n × k) steps. With n = 105 and k = 5 × 104, it is billions of steps. But two windows that sit next to each other share k − 1 items. Adding those items again and again is wasted work. Lesson 11 told us to look for this kind of waste.

Slide: add one, remove one

When the window moves one step to the right, only two items change. One item enters on the right and one item leaves on the left. So do not add everything again. Just update the sum:

Sliding window update

sum = sum + nums[i] − nums[i − k]. Add the new item, and take away the item that is k positions back.

Traced: largest sum of 3 neighbours

step 1 / 12
Set up
sum = 2 + 1 + 5 = 8

Build the first window, indices 0..2, the normal way.

1const nums = [2, 1, 5, 1, 3, 2];2const k = 3;▶let sum = nums[0] + nums[1] + nums[2];4let best = sum;5for (let i = k; i < nums.length; i++) {6  sum += nums[i] - nums[i - k];7  best = Math.max(best, sum);8}9console.log(best);
Variables
nums[2, 1, 5, 1, 3, 2]
k3
sum8
Console
(nothing printed yet)
The first window is built the normal way. Every later window costs O(1), which is one quick step. The total is O(n), whatever the value of k is. · interactive
Dry run · the four windows of size 3
WindowItemsSumComputed as
0..22, 1, 58built directly
1..31, 5, 178 + 1 − 2
2..45, 1, 397 + 3 − 1
3..51, 3, 269 + 2 − 5

The fixed-window template

You do not need to build the first window separately. Walk through the array once. At each index, add the item that enters. Remove the item that leaves, but only once the window is full. Use the window when it is complete:

JavaScript
function fixedWindow(nums, k) {  let state = 0;                         // e.g. a sum or a count  let best = -Infinity;  for (let i = 0; i < nums.length; i++) {    state += nums[i];                    // 1. add the item entering on the right    if (i >= k) state -= nums[i - k];    // 2. remove the item that fell off the left    if (i >= k - 1) {                    // 3. a full window ends at i: use it      best = Math.max(best, state);    }  }  return best;}console.log(fixedWindow([2, 1, 5, 1, 3, 2], 3)); // 9

The three numbered steps are the whole pattern. Only the state changes from problem to problem. The state is the information you keep about the window, such as a sum, a count, or a frequency map (a Map that counts each value). Check the two conditions with care. i >= k means “an item must leave”. i >= k - 1 means “a full window exists”. These are the places where off-by-one mistakes (being wrong by exactly one) usually happen.

Averages of every window

A moving average is the average of the last k values, and it updates as the window moves. People use it for prices or sensor readings. It is the window sum divided by k:

JavaScript
function windowAverages(nums, k) {  const out = [];  let sum = 0;  for (let i = 0; i < nums.length; i++) {    sum += nums[i];    if (i >= k) sum -= nums[i - k];    if (i >= k - 1) out.push(sum / k);  }  return out;}console.log(windowAverages([1, 3, 2, 6, -1, 4, 1, 8, 2], 5)); // [ 2.2, 2.8, 2.4, 3.6, 2.8 ]

Counting inside a window

The state can be a count instead of a sum. In this example we find the most vowels (a, e, i, o, u) in any k letters in a row.

JavaScript
function maxVowels(s, k) {  const isVowel = (ch) => "aeiou".includes(ch);  let count = 0, best = 0;  for (let i = 0; i < s.length; i++) {    if (isVowel(s[i])) count++;                     // entering    if (i >= k && isVowel(s[i - k])) count--;       // leaving    if (i >= k - 1) best = Math.max(best, count);  }  return best;}console.log(maxVowels("abciiidef", 3)); // 3   ("iii")

A window with a frequency map

Sometimes the question is about which values are in the window. For example: how many distinct (different) values, anagrams, or duplicates. Then the state is a frequency map (Lesson 15). A frequency map is a Map that stores how many times each value appears. Add one to the count of the value that enters. Subtract one from the count of the value that leaves. Delete a key when its count reaches 0. This keeps freq.size equal to the number of distinct values.

JavaScript
function distinctInWindows(nums, k) {  const freq = new Map();  const out = [];  for (let i = 0; i < nums.length; i++) {    freq.set(nums[i], (freq.get(nums[i]) ?? 0) + 1);       // add the new item    if (i >= k) {      const old = nums[i - k];      freq.set(old, freq.get(old) - 1);                    // remove the old item      if (freq.get(old) === 0) freq.delete(old);           // keep size = distinct count    }    if (i >= k - 1) out.push(freq.size);  }  return out;}console.log(distinctInWindows([1, 2, 1, 3, 4, 2, 3], 4)); // [ 3, 4, 4, 3 ]
Dry run · distinct values in each window of size 4
WindowItemsMap after updateDistinct
0..31, 2, 1, 3{1: 2, 2: 1, 3: 1}3
1..42, 1, 3, 4{1: 1, 2: 1, 3: 1, 4: 1}4
2..51, 3, 4, 2{1: 1, 3: 1, 4: 1, 2: 1}4
3..63, 4, 2, 3{3: 2, 4: 1, 2: 1}3

In the last step, 1 leaves with count 0 and is deleted, so the size drops to 3.

Practice questions

For each question, first decide what the window state is (a sum, a count or a map). Then fill in the template.

Q1Maximum average subarrayEasy

Find the largest average of any k values in a row.

Example 1
Input: nums = [1, 12, -5, -6, 50, 3], k = 4
Output: 12.75
Explanation: The window [12, −5, −6, 50] has sum 51; 51 / 4 = 12.75.
Example 2
Input: nums = [5], k = 1
Output: 5
Explanation: One window.
Stuck? Open one hint at a time
Hint 1
The largest average comes from the largest sum divided by k. So find the largest window sum.
Show the solution with explanations
Approach 1Sliding window sum
The idea

Keep the window sum. At the end, divide the best sum by k.

JavaScript
function findMaxAverage(nums, k) {  let sum = 0, best = -Infinity;  for (let i = 0; i < nums.length; i++) {    sum += nums[i];    if (i >= k) sum -= nums[i - k];    if (i >= k - 1) best = Math.max(best, sum);  }  return best / k;}console.log(findMaxAverage([1, 12, -5, -6, 50, 3], 4)); // 12.75console.log(findMaxAverage([5], 1));                    // 5
How it works

O(n) time, O(1) space. best starts at -Infinity (smaller than any number) because sums can be negative.

Which approach should you use?

Divide once at the end, not at every step. This needs fewer operations and avoids rounding problems when you compare numbers. (LeetCode 643.)

Q2Maximum vowels in a substring of length kMedium

Return the largest number of vowels in any substring (a block of letters in a row) of s that has length k.

Example 1
Input: s = "abciiidef", k = 3
Output: 3
Explanation: "iii" has three vowels.
Example 2
Input: s = "leetcode", k = 3
Output: 2
Explanation: "lee", "eet" and "ode" each have two.
Example 3
Input: s = "rhythms", k = 4
Output: 0
Explanation: No vowels at all.
Stuck? Open one hint at a time
Hint 1
The window state is the number of vowels inside the window.
Show the solution with explanations
Approach 1Sliding count
The idea

Add 1 when a vowel enters. Subtract 1 when a vowel leaves.

JavaScript
function maxVowels(s, k) {  const vowels = new Set("aeiou");  let count = 0, best = 0;  for (let i = 0; i < s.length; i++) {    if (vowels.has(s[i])) count++;    if (i >= k && vowels.has(s[i - k])) count--;    if (i >= k - 1) best = Math.max(best, count);    if (best === k) break;                 // cannot do better than all vowels  }  return best;}console.log(maxVowels("abciiidef", 3)); // 3console.log(maxVowels("leetcode", 3));  // 2console.log(maxVowels("rhythms", 4));   // 0
How it works

O(n) time, O(1) space. The early exit is optional. It shows that you know the biggest possible answer is k.

Which approach should you use?

This uses the template directly, with a count as the state. (LeetCode 1456.)

Q3Windows with average at least a thresholdMedium

Count the windows of size k that have an average of at least threshold.

Example 1
Input: arr = [2, 2, 2, 2, 5, 5, 5, 8], k = 3, threshold = 4
Output: 3
Explanation: [2, 5, 5], [5, 5, 5] and [5, 5, 8] have averages 4, 5 and 6.
Stuck? Open one hint at a time
Hint 1
“Average ≥ threshold” is the same as “sum ≥ threshold × k”. So you do not need division.
Show the solution with explanations
Approach 1Sliding sum, compare with k × threshold
The idea

Count the full windows whose sum is at least k * threshold.

JavaScript
function numOfSubarrays(arr, k, threshold) {  const need = k * threshold;  let sum = 0, count = 0;  for (let i = 0; i < arr.length; i++) {    sum += arr[i];    if (i >= k) sum -= arr[i - k];    if (i >= k - 1 && sum >= need) count++;  }  return count;}console.log(numOfSubarrays([2, 2, 2, 2, 5, 5, 5, 8], 3, 4)); // 3
How it works

O(n) time. If you compare whole-number sums, you never work with decimal averages.

Which approach should you use?

It is a useful small trick to move a division to the other side of a comparison. (LeetCode 1343.)

Q4K-radius averagesMedium

For each index, return the average (rounded down) of the values that are within distance k of it. Return −1 if there are fewer than k values on either side.

Example 1
Input: nums = [7, 4, 3, 9, 1, 8, 5, 2, 6], k = 3
Output: [-1, -1, -1, 5, 4, 4, -1, -1, -1]
Explanation: Index 3 averages indices 0..6: 37 / 7 = 5 (rounded down). An index with fewer than k neighbours on one side gets −1.
Example 2
Input: nums = [100000], k = 0
Output: [100000]
Explanation: k = 0: each value is its own average.
Stuck? Open one hint at a time
Hint 1
The window around index c goes from c − k to c + k, so its size is 2k + 1.
Hint 2
Slide a window of size 2k + 1. When it ends at i, its centre is at i − k.
Show the solution with explanations
Approach 1Fixed window of size 2k + 1
The idea

Fill the result with −1. Slide a window of size 2k + 1, and write each average at the centre of the window.

JavaScript
function getAverages(nums, k) {  const size = 2 * k + 1;  const out = new Array(nums.length).fill(-1);  let sum = 0;  for (let i = 0; i < nums.length; i++) {    sum += nums[i];    if (i >= size) sum -= nums[i - size];    if (i >= size - 1) out[i - k] = Math.floor(sum / size);  }  return out;}console.log(getAverages([7, 4, 3, 9, 1, 8, 5, 2, 6], 3)); // [ -1, -1, -1, 5, 4, 4, -1, -1, -1 ]console.log(getAverages([100000], 0));                    // [ 100000 ]
How it works

O(n) time. The only new idea is this link: “the window ends at i” means “the centre is i − k”.

Which approach should you use?

Some problems describe a window by its centre and radius. First change that into a start and an end. (LeetCode 2090.)

Q5Grumpy bookstore ownerMedium

At minute i, customers[i] people visit a shop. They are happy unless the owner is grumpy (grumpy[i] = 1). The owner can use a technique once to stay calm for minutes minutes in a row. Return the largest number of happy customers.

Example 1
Input: customers = [1, 0, 1, 2, 1, 1, 7, 5], grumpy = [0, 1, 0, 1, 0, 1, 0, 1], minutes = 3
Output: 16
Explanation: When the owner is not grumpy, the customers are always happy: 1 + 1 + 1 + 7 = 10. Using the calm technique on minutes 5–7 saves 1 + 5 = 6 more customers.
Stuck? Open one hint at a time
Hint 1
Split the answer in two parts: the customers who are happy anyway, plus the extra customers that the technique saves.
Hint 2
The extra customers are a window sum that counts only the grumpy minutes. Find the best window of size minutes.
Show the solution with explanations
Approach 1Base sum + best window of gains
The idea

Use a window sum. An item counts only when the owner is grumpy at that minute.

JavaScript
function maxSatisfied(customers, grumpy, minutes) {  let base = 0;  for (let i = 0; i < customers.length; i++) {    if (grumpy[i] === 0) base += customers[i];  }  let gain = 0, bestGain = 0;  for (let i = 0; i < customers.length; i++) {    if (grumpy[i] === 1) gain += customers[i];    if (i >= minutes && grumpy[i - minutes] === 1) gain -= customers[i - minutes];    bestGain = Math.max(bestGain, gain);  }  return base + bestGain;}console.log(maxSatisfied([1, 0, 1, 2, 1, 1, 7, 5], [0, 1, 0, 1, 0, 1, 0, 1], 3)); // 16
How it works
Dry run · gain in each window of 3 minutes
WindowGrumpy customers insideGain
0..200
1..30 + 22
2..422
3..52 + 13
4..611
5..71 + 56
Which approach should you use?

O(n) time. The key step is to separate the fixed part of the answer from the part that depends on the window. (LeetCode 1052.)

Q6Does any window contain a duplicate?Easy

Return true if nums has two equal values that are at most k positions apart.

Example 1
Input: nums = [1, 2, 3, 1], k = 3
Output: true
Explanation: The two 1s are 3 positions apart. That is at most k = 3, so they fit in one window.
Example 2
Input: nums = [1, 2, 3, 1, 2, 3], k = 2
Output: false
Explanation: Equal values are always 3 apart, and 3 is more than k = 2.
Stuck? Open one hint at a time
Hint 1
Keep a Set (a collection of different values) of the last k values. Before you add a value, check the Set.
Show the solution with explanations
Approach 1Set as a sliding window
The idea

The Set holds the values of the previous k positions. Remove the value that moves out of range.

JavaScript
function containsNearbyDuplicate(nums, k) {  const window = new Set();  for (let i = 0; i < nums.length; i++) {    if (window.has(nums[i])) return true;    window.add(nums[i]);    if (i >= k) window.delete(nums[i - k]);    // keep only the last k values  }  return false;}console.log(containsNearbyDuplicate([1, 2, 3, 1], 3));       // trueconsole.log(containsNearbyDuplicate([1, 2, 3, 1, 2, 3], 2)); // false
How it works

O(n) time, O(k) space. Here the window state is a Set, not a sum. Lesson 26 solves the same problem with a Map of the last position of each value.

Which approach should you use?

Any data structure can be the window state, if it can add and remove one item in O(1). (LeetCode 219.)

Make it stick

Make it stick — close this page, then
  1. Write the fixed-window template from memory, with its three steps.
  2. Explain why the sliding window is O(n) while the brute force is O(n × k).
  3. Say what goes wrong if you forget to delete map keys whose count reaches 0.

Revisit schedule: solve two of the questions again tomorrow, again in 3 days, and once more in a week — without looking at the answers. Each time it will feel easier. That is the memory forming.

What's next

In this lesson the window size was fixed. In Lesson 23 the window grows and shrinks. It gets bigger while a rule is true, and it gets smaller when the rule breaks. This is the technique behind “longest substring without repeating characters” and many other popular problems.

Practise on LeetCode

Real interview problems that use exactly what this lesson teaches. Solve them in JavaScript after the practice questions above.

Published by Jainik · Last updated · How these lessons are made

Keep learning

Part of the study guide

Related series