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_leftandlast_rightwhile 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
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 rowsCommon 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.)