The Last Known Good

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

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.

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

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
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

Common follow-up questions