Sign in to run and submit your work
Reading is open to everyone. Running code and saving drafts need an account so your work is yours and comes back on your next visit.
or
CODE WORKSPACE
Events are sometimes delivered again after a retry, so the same event_id can appear several times with different ingested_at times. The table should have one row per event: the one that was ingested last. If two rows have the same time, the row with updated_by filled in is better than one with None. If they are still tied, the one that comes later in the list wins.
Write keep_latest(rows). rows is a list of dicts with event_id, ingested_at (a number) and updated_by (a string or None). Return one row for each event, sorted by event_id.
Function to write
keep_latest(rows: list[dict]) -> list[dict]One row per event_id: the latest ingested_at, then the one with updated_by filled, then the later one in the list; sorted by event_id.
How to approach it
Build one tuple for each row that ranks it: the time first, then whether updated_by is filled, then its position. Keep the row with the biggest tuple for each event_id.
Sample cases
+ 2 held back until you submit
a retried event
`e1` has three rows. The one ingested at 120 wins, even though a row with 90 comes after the one with 100 in the list.
Input
Argument 1
| event_id | ingested_at | updated_by |
|---|---|---|
| e1 | 100 | loader |
| e2 | 105 | None |
| e1 | 90 | retry |
| e1 | 120 | fixer |
Returns
| event_id | ingested_at | updated_by |
|---|---|---|
| e1 | 120 | fixer |
| e2 | 105 | None |
a tie on time
Same time. The row with `updated_by` filled in wins over the one with `None`.
Input
Argument 1
| event_id | ingested_at | updated_by |
|---|---|---|
| e1 | 50 | None |
| e1 | 50 | alice |
Returns
| event_id | ingested_at | updated_by |
|---|---|---|
| e1 | 50 | alice |
a full tie
Same time and both filled in. The later row in the list wins.
Input
Argument 1
| event_id | ingested_at | updated_by | note |
|---|---|---|---|
| e1 | 50 | alice | first |
| e1 | 50 | bob | second |
Returns
| event_id | ingested_at | updated_by | note |
|---|---|---|---|
| e1 | 50 | bob | second |
no rows
Nothing to keep gives an empty list.
Input
Argument 1
[] (empty list)
Returns
[] (empty list)
the latest row is first in the list
The newest row is listed first. Keeping the last row in the list would keep the old one.
Input
Argument 1
| event_id | ingested_at | updated_by |
|---|---|---|
| e1 | 300 | new |
| e1 | 10 | old |
Returns
| event_id | ingested_at | updated_by |
|---|---|---|
| e1 | 300 | new |
Constraints
event_id.ingested_at among the rows with that event_id.ingested_at, prefer a row whose updated_by is not None.event_id. Return copies; do not change the input rows.Worked example
Take the rows: e1 at 100 by loader, e2 at 105 with None, e1 at 90 by retry, and e1 at 120 by fixer. Walk the list and remember the best row for each event. e1 at 100 is the first for e1. e2 is the first for e2. e1 at 90 is older than 100, so it loses. e1 at 120 is newer than 100, so it wins. The answer, sorted by event_id, is the e1 row at 120 and then the e2 row.
The trap: writing each row into a dict by its key keeps the last row in the list. Here that is the right one only by luck. If the latest row came first in the list, you would keep an old row.
What this tests
Deduplicate with a tiebreaker, the Python version of ROW_NUMBER() OVER (PARTITION BY event_id ORDER BY ingested_at DESC) = 1. The skill is to turn the rules into one comparable key: a tuple of the time, whether updated_by is filled, and the position. Python compares tuples left to right, so each part only matters when the earlier ones tie. This is a very common data engineering screen question.
keep_latest(rows: list[dict]) -> list[dict]Submit for review to find out what your query gets right, what it gets wrong, and how it compares with the best working query for this exercise.
This scenario runs a full workspace — editor, canvas and results side by side. It needs a laptop or desktop to be usable. Open this page on a bigger screen to start building.