# One Step Behind

> What came before this row?

Canonical URL: <https://datadriven.io/problems/one_step_behind>

Domain: Python · Difficulty: medium · Seniority: L3

## Problem

You're aligning a stream of sensor readings so each one can sit beside the reading that came just before it. Return a list the same length as the input, where each slot carries its predecessor's value.

## Worked solution and explanation

### The real problem

Underneath the lag framing this is a boundary problem, not a shifting problem. Carrying each value into the next slot is the easy part; the tell is what you seed the output with and where its length comes from. The shortcut almost everyone reaches for seeds `result = [None]` and copies from index 1, which reads fine until an empty list arrives and you hand back `[None]` instead of `[]`. That one phantom row breaks the same-length promise every downstream column-align step leans on.

---

### Walk it

#### Step 1: Carry the previous value forward

Keep a single `prev` variable. Before you read each value, the answer for the current slot is whatever you saw last time, which starts as None. Append `prev`, then update it to the current value. The first append naturally lands None because nothing came before.

#### Step 2: Let the input drive the length

The output grows one slot per input element, so its length is the input's length by construction. That is the whole fix: never seed a slot the input did not ask for. Empty input walks zero times and returns an empty list; a single element appends exactly one None.

---

### The solution

**Carry the predecessor forward, one slot per input element**

```python
def lag(values):
    result = []
    prev = None
    for value in values:
        result.append(prev)
        prev = value
    return result
```

*Length comes from the loop, so empty in gives empty out.*

> **Time and space**
>
> **Time:** O(n), a single pass over the input.
> 
> **Space:** O(n) for the output list, which is unavoidable since you return a new same-length list.

**Seed-and-copy (breaks on empty)**

`result = [None]` then loop `range(1, len(values))`. Correct on every non-empty input, but an empty list returns `[None]`, a length-1 result where the contract demands length 0.

**Carry-forward (length from input)**

Start `result = []` and append one slot per element. The length is whatever the input's length is, so the empty case falls out as `[]` with no special-casing.

> **Common pitfall**
>
> Returning `[None]` for an empty input instead of `[]`. It hides because it only surfaces when the list is empty, and most people never test that path. Derive the length from the input and it cannot happen.

> **Interviewers watch for**
>
> Whether you reach for a deque or a sliding window when a plain offset is all this needs. The direct carry-forward is the clearest form, and handling the empty case without a special branch is the real signal of care.

---

## Common follow-up questions

- How would you generalize this to a lag of k positions? _(Tests prepending k Nones and taking values[:-k], while still deriving the length from the input.)_
- How would you implement a lead (forward-looking) version? _(Tests the mirror operation: each slot carries the NEXT value, with None filling the final slot.)_
- How does this relate to SQL's LAG() window function? _(Tests mapping LAG(col, 1) OVER (ORDER BY ...) onto this exact carry-forward pattern.)_

## Related

- [All practice problems](https://datadriven.io/problems)
- [Mock interview mode](https://datadriven.io/interview/one_step_behind)
- [Python Interview Questions](https://datadriven.io/python-interview-questions)
- [Data Engineering Interview Prep Guide](https://datadriven.io/data-engineer-interview-prep)
- [Daily Challenge](https://datadriven.io/daily)

---

Source: DataDriven (https://datadriven.io). DataDriven is the data engineering interview community. Live code execution in SQL, Python, and Spark sandboxes. Every feature is open to every member.