LESSON 114 · DEEPER PYTHON30 min

Compute a Moving Average with a Sliding Window

Return every fixed-size moving average with one pass and a running sum.

Before you start: Lists · Loops · Functions

Example check: Worked and guided code samples are checked against CPython 3.13. Practice uses Pyodide 0.29.5 (Python 3.13) in your browser. How we check lessons

Video lesson: Compute a Moving Average with a Sliding Window

The preview is stored on this site. YouTube loads only when you press Play. Watch on YouTube

Read the video transcript

Select a timestamp to watch that moment on YouTube.

  1. 00:00

    Five readings with a three-reading window should yield three averages. Run the loop. Why does its output stop after 2.0 and 3.0? The broken range ends before the last valid start index. Its terminal list has only two averages and silently drops the newest reading's window. The expected count is five minus three plus one, or three, not five minus three. Calculate starting positions rather than staring at the range syntax. With five readings and a width of three, a window may start at index zero, one, or two. The first two contain one-two-three and two-three-four; the omitted last one contains three-four-five.

  2. 00:41

    Python range stops before its upper bound, so an end of len minus size produces starts zero and one only. The terminal's two averages are individually correct, which makes this missing final result easy to overlook. The off-by-one comes from confusing a count with the exclusive end of range. There are len minus size plus one valid start positions, so the upper bound given to range must be exactly that number. This includes the first position and the last position where a full window still fits. If the input has exactly size elements, the broken loop runs zero times even though one full window exists.

  3. 01:22

    The diagnostic program constructs the three slices explicitly and prints them. It is intentionally simple rather than optimized, giving us a trustworthy small- data oracle. We can compare both the number of results and each value against this direct slicing version. For a long live sensor stream we would not create every slice just to compute a mean, but that slower reference is excellent for testing the optimized loop. List the data windows beside their sums. One plus two plus three is six, giving mean two. Drop one and add four to get nine, giving mean three. Drop two and add five to get twelve, giving mean four.

  4. 02:03

    We did not sum a whole three-item slice on every step. That saving matters when the window is large or when readings arrive one at a time. The visible list of three windows is also a simple oracle for the expected output length. The invariant behind the fast loop is that total equals the sum of the current full window immediately before we append its average. When advancing, we must remove exactly the element that is leaving and include exactly the element entering. Removing the wrong index may still produce a plausible number for one step but diverges later. For a small test, calculate the same averages with direct slices and compare;

  5. 02:43

    the slow reference makes a strong correctness check for the faster update rule. The repaired function records the first window's mean, then adds the incoming reading and subtracts the outgoing one at each step. The terminal prints [2.0, 3.0, 4.0], including the final 4.0 that was missing. It rejects zero or negative sizes and returns no complete windows when size exceeds the input. The first sum must include exactly the first size elements. After recording its average, the loop's next index identifies the incoming value. The outgoing value sits size positions before it.

  6. 03:24

    Subtract one, add the other, and divide the updated sum by the unchanged window width. Append the mean after each update, not before it, so the printed sequence ends in four. The function uses constant work for each new full window. Decide what a moving-average result represents before graphing it. These are trailing full windows: the first result belongs after the third reading, when three values are available. The function does not invent a partial average for the first or second reading. A chart that expects one point per input must either align the shorter result series correctly or define a separate rule for partial windows.

  7. 04:06

    A width equal to the number of readings gives exactly one full window; a larger width gives none. The final terminal prints those two cases separately. Zero width should raise instead of dividing by zero or producing meaningless empty windows. The example numbers divide cleanly, but decimal sensor values often produce floating-point rounding differences; compare expected means with a tolerance. If readings stream indefinitely, retain only the outgoing values needed for the next subtraction. A property test can generate many short numeric lists and compare this running-sum function with the straightforward implementation that sums each full slice.

  8. 04:48

    Include width one, width equal to list length, and an empty input. That approach detects off-by-one mistakes beyond the single visible example. For floating-point inputs, use a tolerance because two valid orders of addition can produce slightly different low-order bits. Report means with their window alignment, not just as an unlabeled array.

Understand the concept

A sensor records five readings, and an analyst wants the average of each consecutive three-reading block. There are three windows: positions zero to two, one to three, and two to four. A sliding window keeps a running sum so the next average only adds the new reading and removes the old one.

A range ending at len(values) - size omits the final valid starting position. The broken run prints two averages instead of three. A function can pass tests for the first window while silently losing the newest data point, which is often the most important in a time series.

Calculate the expected number of windows as len(values) - size + 1 when enough values exist. For five readings and size three, that is three. The first sum is one plus two plus three; the next replaces one with four; the last replaces two with five. Initialize a sum for the first size values, append its average, then for each later index add values[index] and subtract values[index - size]. Append one average after each update. Reject nonpositive window sizes, and return an empty list when size exceeds the data length.

A window size equal to the data length yields one average. A window size larger than the data yields none. The final run checks both so the implementation does not read beyond the end or invent a partial window. Floating-point averages can have tiny representation differences; tests should use appropriate tolerance for nonexact decimals. The lesson uses small integer inputs whose output is exactly representable. For streaming data too large to store, the same running-sum idea can be paired with a bounded deque.

  • A sensor records five readings, and an analyst wants the average of each consecutive three-reading block.
  • A range ending at len(values) - size omits the final valid starting position.
  • Initialize a sum for the first size values, append its average, then for each later index add values[index] and subtract values[index - size].
  • A window size equal to the data length yields one average.

See it step by step

Read the code, predict the output, then compare it with the result.

01. Count all valid windows

PYTHON
values=[1,2,3,4,5]; size=3
print(len(values)-size+1)
print([values[i:i+size] for i in range(len(values)-size+1)])
EXPECTED OUTPUT
3
[[1, 2, 3], [2, 3, 4], [3, 4, 5]]

The plus one includes the final full window starting at index two.

02. Update a running sum

PYTHON
values=[1,2,3,4,5]; size=3
total=sum(values[:size])
print(total)
total += values[3]-values[0]
print(total)
EXPECTED OUTPUT
6
9

The next window removes its outgoing value and adds its incoming value.

03. Compute the three averages

PYTHON
values=[1,2,3,4,5]; size=3
total=sum(values[:size]); result=[total/size]
for i in range(size,len(values)):
    total+=values[i]-values[i-size]
    result.append(total/size)
print(result)
EXPECTED OUTPUT
[2.0, 3.0, 4.0]

One initial sum and two constant-time updates produce the complete output.

A closer look

Follow the reasoning, inspect each result, then try the suggested changes in the console below.

01 / 03

Observe the first result

A sensor records five readings, and an analyst wants the average of each consecutive three-reading block. There are three windows: positions zero to two, one to three, and two to four. A sliding window keeps a running sum so the next average only adds the new reading and removes the old one.

The plus one includes the final full window starting at index two. Calculate the expected number of windows as len(values) - size + 1 when enough values exist. For five readings and size three, that is three. The first sum is one plus two plus three; the next replaces one with four; the last replaces two with five.

PYTHON
values=[1,2,3,4,5]; size=3
print(len(values)-size+1)
print([values[i:i+size] for i in range(len(values)-size+1)])
EXPECTED OUTPUT
3
[[1, 2, 3], [2, 3, 4], [3, 4, 5]]
Follow the reasoning
  1. Predict the first sample's printed value and type.
  2. Run it, then compare with the expected output.
02 / 03

Change the rule deliberately

A range ending at len(values) - size omits the final valid starting position. The broken run prints two averages instead of three. A function can pass tests for the first window while silently losing the newest data point, which is often the most important in a time series.

The next window removes its outgoing value and adds its incoming value. Initialize a sum for the first size values, append its average, then for each later index add values[index] and subtract values[index - size]. Append one average after each update. Reject nonpositive window sizes, and return an empty list when size exceeds the data length. After the first sum, loop index from size to len(values)-1; update total by adding values[index] and subtracting values[index-size].

PYTHON
values=[1,2,3,4,5]; size=3
total=sum(values[:size])
print(total)
total += values[3]-values[0]
print(total)
EXPECTED OUTPUT
6
9
Follow the reasoning
  1. Identify the line that changes the rule.
  2. Run the corrected sample with the same input.
03 / 03

Verify the boundary

Calculate the expected number of windows as len(values) - size + 1 when enough values exist. For five readings and size three, that is three. The first sum is one plus two plus three; the next replaces one with four; the last replaces two with five. Initialize a sum for the first size values, append its average, then for each later index add values[index] and subtract values[index - size]. Append one average after each update. Reject nonpositive window sizes, and return an empty list when size exceeds the data length.

One initial sum and two constant-time updates produce the complete output. A window size equal to the data length yields one average. A window size larger than the data yields none. The final run checks both so the implementation does not read beyond the end or invent a partial window. Floating-point averages can have tiny representation differences; tests should use appropriate tolerance for nonexact decimals. The lesson uses small integer inputs whose output is exactly representable. For streaming data too large to store, the same running-sum idea can be paired with a bounded deque.

PYTHON
values=[1,2,3,4,5]; size=3
total=sum(values[:size]); result=[total/size]
for i in range(size,len(values)):
    total+=values[i]-values[i-size]
    result.append(total/size)
print(result)
EXPECTED OUTPUT
[2.0, 3.0, 4.0]
Follow the reasoning
  1. Try the documented edge case.
  2. Explain why the output satisfies the function contract.

Try it in Python

Edit the example and run it. Python starts in your browser the first time you click Run.

Python console

Ready to run
Need input()? Add one value per line
Ctrl / ⌘ + Enter to run
OUTPUT
Your output appears here.
Your turnReturn each full-window average using a running sum. The prepared call must print [2.0, 3.0, 4.0]. Reject size <= 0 with ValueError and return [] when size exceeds the list.
Need a hint?

After the first sum, loop index from size to len(values)-1; update total by adding values[index] and subtracting values[index-size].

Complete the task and select Check task to verify your code.

    Quick quiz

    Three questions. You can change your answers and try again.

    01.How many size-3 windows are in five values?

    02.What changes when a window slides one step?

    03.What should size larger than the data return here?

    Typical mistakes

    Everyone meets these errors. See what causes them and how to fix them.

    The visible failure

    COMMON MISTAKE
    range(len(values) - size)
    FIX
    initialize first window, then update through index len(values)-1

    What happens: A range ending at len(values) - size omits the final valid starting position.
    Initialize a sum for the first size values, append its average, then for each later index add values[index] and subtract values[index - size]. Append one average after each update. Reject nonpositive window sizes, and return an empty list when size exceeds the data length.

    Missing the edge condition

    COMMON MISTAKE
    average a partial final window
    FIX
    return [] if size > len(values)

    What happens: A window size equal to the data length yields one average.
    Floating-point averages can have tiny representation differences; tests should use appropriate tolerance for nonexact decimals. The lesson uses small integer inputs whose output is exactly representable. For streaming data too large to store, the same running-sum idea can be paired with a bounded deque.

    Browse this lesson’s problem fixes

    Sources and further reading