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
A partner feed writes raw event payloads straight into the landing zone, and the analytics tables downstream expect them clean. The feed is hand-rolled: type arrives in mixed case and sometimes padded, ts is a string in more than one format, and a corrected copy of an event is re-sent under the same id.
Write normalize_events(rows). It takes the raw list and returns the cleaned events, oldest first.
Function to write
normalize_events(rows: list[dict]) -> list[dict]A list of dicts with the keys id, type, ts and user_id, sorted by ts then id.
How to approach it
Validate each row before you keep it, normalise what survives, then resolve the duplicates.
Sample cases
+ 2 held back until you submit
the raw feed
Every rule at once: casing, padding, a bad timestamp, an unknown type, a blank id, an offset, a naive timestamp and a redelivery.
Input
Argument 1
| id | type | ts | user_id |
|---|---|---|---|
| e1 | click | 2026-03-02T09:00:00Z | u1 |
| e2 | VIEW | 2026-03-02T09:05:00Z | u2 |
| e3 | purchase | 2026-03-02T08:30:00Z | u1 |
| e4 | click | not a timestamp | u3 |
| e5 | scroll | 2026-03-02T09:10:00Z | u2 |
| click | 2026-03-02T09:12:00Z | u4 | |
| e6 | click | 2026-03-02T09:15:00+02:00 | u5 |
| e7 | view | 2026-03-02T09:20:00 | |
| e1 | click | 2026-03-02T09:30:00Z | u1 |
Returns
| id | type | ts | user_id |
|---|---|---|---|
| e6 | click | 2026-03-02T07:15:00+00:00 | u5 |
| e3 | purchase | 2026-03-02T08:30:00+00:00 | u1 |
| e2 | view | 2026-03-02T09:05:00+00:00 | u2 |
| e7 | view | 2026-03-02T09:20:00+00:00 | None |
| e1 | click | 2026-03-02T09:30:00+00:00 | u1 |
an empty feed
No rows in, no rows out — and no crash on the empty case.
Input
Argument 1
[] (empty list)
Returns
[] (empty list)
three copies of one event
The same id three times, out of order. The latest timestamp wins, so type is purchase.
Input
Argument 1
| id | type | ts | user_id |
|---|---|---|---|
| d1 | view | 2026-03-01T10:00:00Z | u9 |
| d1 | purchase | 2026-03-01T12:00:00Z | u9 |
| d1 | click | 2026-03-01T11:00:00Z | u9 |
Returns
| id | type | ts | user_id |
|---|---|---|---|
| d1 | purchase | 2026-03-01T12:00:00+00:00 | u9 |
Constraints
id is a non-empty string, type is one of click, view or purchase once trimmed and lowercased, and ts parses.ts arrives as 2026-03-02T09:00:00Z, with an offset like +02:00, or with no zone at all. Return every timestamp as UTC in ISO-8601 form.user_id is optional: return None when it is missing, blank or whitespace.id appears more than once, keep the row with the latest ts.ts, then by id. Each one is a dict with exactly the keys id, type, ts, user_id.rows itself must not be modified.Worked example
Event e6 arrives as 2026-03-02T09:15:00+02:00. That is 07:15 UTC — earlier than e2 at 09:05Z, even though it reads later on the page. Sorting the strings as they arrive puts it in the wrong place.
Event e1 appears twice: once at 09:00Z and again at 09:30Z. One row survives, the 09:30 one. Keeping both would silently double every count built on this table, and keeping the first would discard the correction the feed sent on purpose.
What this tests
Defensive parsing of a feed you do not control: validating before trusting, normalising into one representation, and choosing a deterministic winner when the same key arrives twice.
normalize_events(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.