# The Last Known Good

> When a column goes quiet, its last reading keeps standing.

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

Domain: Python · Difficulty: medium · Seniority: L4

## Problem

You're aligning two parallel columns from a sensor export, `left` and `right`, that can stop reporting at different points. Pair them position by position into `[left_i, right_i]` rows for as many positions as the longer column has, and when one column runs out partway, carry its last reported value forward to fill its side of the remaining rows. If a column is empty from the start there's no value to carry, so leave that side as `None`.

## Example 1

Input:

```
left = [1,2,3], right = [10,20]
```

Output:

```
[[1,10],[2,20],[3,20]]
```

## Example 2

Input:

```
left = [5], right = [7,8,9]
```

Output:

```
[[5,7],[5,8],[5,9]]
```

## Worked solution and explanation

### This is forward-fill wearing a list costume

Beneath the framing, the real question is what you do when one column runs dry before the other. Anyone can pair values while both sides still have them; the tell is the gap. Reach for `zip()` and the longer column's tail vanishes. Reach for `itertools.zip_longest()` and the gap fills with a constant `None`, which throws away the last real reading. The skill being probed is driving the loop by the MAX length while remembering the most recent value seen on each side, so the exhausted column keeps reporting its last known value instead of a hole.

**Naive: `zip_longest` with None fill**

`zip_longest`(left, right) pads the exhausted side with a constant None. On left=[1,2,3], right=[10,20] you get a final [3, None], which drops the real last reading 20.

**Correct: carry the last value**

Track `last_left` and `last_right` while looping range(max(len(left), len(right))). The exhausted side keeps its most recent value, so the final row is [3, 20], the last known good.

---

### Building the loop

#### Step 1: Drive the loop by the maximum length

The output has one row per position of the LONGER column, so the bound is max(len(left), len(right)). Anchoring on either column alone is the truncation bug in disguise: the shorter one ends the loop early and the tail disappears.

#### Step 2: Remember the last value seen on each side

Keep a `last_left` and `last_right`, each starting at None. At every index, update a side only when the index is still inside that column; otherwise it retains whatever it last held. A column that has not produced anything yet stays None, which is exactly the empty-from-the-start case.

---

### The solution

**Max-length loop that carries each side's last value forward**

```python
def align_columns(left, right):
    rows = []
    last_left = None
    last_right = None
    span = len(left) if len(left) > len(right) else len(right)
    for i in range(span):
        if i < len(left):
            last_left = left[i]
        if i < len(right):
            last_right = right[i]
        rows.append([last_left, last_right])
    return rows
```

> **Time and Space Complexity**
>
> **Time:** O(max(n, m)) where n and m are the two column lengths, one pass over the longer column.
> 
> Space: O(max(n, m)) for the result list of pairs; the two carry variables are O(1).

> **Interviewers Watch For**
>
> Whether you notice that `itertools.zip_longest` does NOT solve this. Naming it signals stdlib awareness, but recognizing that its constant fill is the wrong behavior here, and that you need per-side state instead, is the senior tell.

> **Common Pitfall**
>
> Padding the gap with a literal None (or grabbing `zip_longest`) instead of the last observed value. On equal-length inputs both look identical, so the bug hides until a real export has a column that drops out early.

---

## Common follow-up questions

- How would you carry values forward across more than two columns at once? _(Tests generalizing the per-side state tracking to N columns with a list of last-seen values.)_
- What if gaps in the MIDDLE of a column, not just the trailing tail, also needed the last value carried? _(Tests real last-observation-carried-forward: treating an interior None as missing and holding the prior value.)_
- How does this relate to a SQL window function that fills nulls with the last non-null value? _(Tests connecting the loop to `LAST_VALUE` / IGNORE NULLS over an ordered partition.)_

## Related

- [All practice problems](https://datadriven.io/problems)
- [Mock interview mode](https://datadriven.io/interview/the_last_known_good)
- [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.