The Forward Fill

An easy Python interview practice problem on DataDriven. Write the Python and run it against test cases, with instant feedback.

Domain
Python
Difficulty
easy
Seniority
L3
Asked in
Meta

Problem

Given a list that may contain None values, return a list of the same length where each None is replaced by the most recent prior non-None value. If a None appears before any non-None value has been seen, leave it as None.

Patch the gaps in a noisy sensor stream.

Example

Input:

values = [1,null,null,2,null]

Output:

[1,1,1,2,2]

Worked solution and explanation

What this really is

Strip off the "noisy sensor stream" costume and this is a stateful scan that carries one value forward: at each position you either see a real reading or a gap, and a gap inherits whatever the last real reading was. Everyone can write a loop. The thing that actually separates candidates is realizing the fill source is the last value you EMITTED, not the element physically before you in the input, and that a run of gaps at the very start has nothing to carry. Reach backward one index instead of tracking state and you either IndexError on the first element or, worse, copy None around silently and 'fill' leading gaps with garbage.

How to reason to it

Step 1: Decide where the fill value comes from

It comes from the OUTPUT stream, not the raw input. Consecutive gaps must all inherit the same carried value, so [1, None, None] becomes [1, 1, 1]. If you instead looked at values[i-1], the second gap would read the first gap (still None on a raw read) and break the run. Tracking last_valid in a variable sidesteps this entirely.

Step 2: Handle the leading-gap case for free

Initialize last_valid to None. A None before any real reading appends None, which is exactly the spec. There is no special 'first element' branch to write; the initial state already encodes 'nothing to carry yet'.

Step 3: Emit exactly one value per input element

Append inside the loop body every iteration, after possibly updating last_valid. This guarantees the output length equals the input length and that real values pass through untouched. Empty input yields an empty list with no special-casing.

Single-pass forward fill
def forward_fill(values: list) -> list:
    result = []
    last_valid = None
    for val in values:
        if val is not None:
            last_valid = val
        result.append(last_valid)
    return result

One pass, one state variable, no index arithmetic.

Look-back by index (fragile)
for i in range(len(values)): if values[i] is None: result.append(values[i-1]) else: result.append(values[i]) On i=0 this reads values[-1] (wraps to the LAST element in Python, a silent bug), and a run of gaps reads raw None neighbors, so [1, None, None] returns [1, 1, None].
Carry a state variable (correct)
last_valid = None for val in values: if val is not None: last_valid = val result.append(last_valid) The carry always reflects the last EMITTED real value, so gap runs fill cleanly and the leading-gap case falls out of the None initializer.

Common follow-up questions