Parse a web server's request log and summarise it. Each line looks like:
2026-03-14T09:05:12 GET /api/items 200 35
timestamp, HTTP method, path, status code, latency in milliseconds, separated by whitespace. Timestamps all use this fixed-width format, so they compare correctly as strings.
Write parse_logs(lines), which returns a tuple (records, malformed).
"ts", "method", "path" (strings, as written) and "status", "latency" (ints), in that key order.Records keep the order of the input lines.
{'ts': '2026-03-14T09:05:12', 'method': 'GET', 'path': '/api/items', 'status': 200, 'latency': 35}
{'ts': '2026-03-14T09:05:13', 'method': 'POST', 'path': '/api/cart', 'status': 201, 'latency': 120}
0records, bad = parse_logs([
"2026-03-14T09:05:12 GET /api/items 200 35",
"2026-03-14T09:05:13 POST /api/cart 201 120",
])
for r in records:
print(r)
print(bad)Parse web server log lines while counting malformed ones, compute per-endpoint request, error and latency stats, find the busiest endpoints in a time window, then answer the same questions over a live stream.
Also known as: Log analyzer
Parsing structured text shows up in 18.5% of the 1,447 coding interview reports we studied, at 15 companies. Turn raw input such as log lines, CSV rows, commands or rule strings into records, then answer questions about them. Later parts usually add malformed input and edge cases.
Reported at: OpenAI, Anthropic, Databricks, Roblox, Harvey AI, Scale AI, Snowflake, Robinhood, Rippling, Perplexity, Citadel, Waymo, Amazon, Zip, xAI.
This is an original practice problem written by AceOffer to train the pattern. It is not a question any company asked.
Follow-ups candidates reported on questions with this pattern, in our words. Parts marked “from reports” below are built on them.
Parse a web server's request log and summarise it. Each line looks like:
2026-03-14T09:05:12 GET /api/items 200 35
timestamp, HTTP method, path, status code, latency in milliseconds, separated by whitespace. Timestamps all use this fixed-width format, so they compare correctly as strings.
Write parse_logs(lines), which returns a tuple (records, malformed).
"ts", "method", "path" (strings, as written) and "status", "latency" (ints), in that key order.Records keep the order of the input lines.
Write endpoint_stats(records).
An endpoint is the method and path joined by a single space, such as "GET /api/items". Return one tuple per endpoint:
(endpoint, requests, errors, avg_latency, max_latency)
requests: number of records for the endpoint,errors: how many of them have status >= 500 (4xx responses are not errors),avg_latency: total latency divided by requests, rounded down (//),max_latency: the largest latency.Sort by requests descending, then by endpoint ascending. No records gives [].
Write top_endpoints(records, start, end, k).
Consider only records with start <= ts < end (start included, end excluded, compared as strings). Count requests per endpoint inside that window and return the k busiest as (endpoint, requests) tuples, sorted by requests descending, then endpoint ascending. If fewer than k endpoints appear in the window, return all of them; if none do, return [].
Now the log is live: lines arrive one at a time and the on-call dashboard asks questions between them. Write a class LogMonitor(window_seconds).
add(line) takes one new line. Apply the Part 1 rules to it: a blank or malformed line is skipped and add returns False. A line whose timestamp is not a real date and time (for example month 13) is also skipped with False. Otherwise the line is accepted and add returns True. Accepted lines arrive in timestamp order: none is earlier than the line before it.T: it holds the accepted lines whose timestamp t satisfies T - window_seconds < t <= T. A line exactly window_seconds older than T has left the window.count(endpoint) returns how many requests for endpoint (method and path, as in Part 2) are in the window, 0 if none or before any line arrives.top(k) returns the k busiest endpoints in the window as (endpoint, requests) tuples, sorted by requests descending, then endpoint ascending.Memory should stay proportional to the lines inside the window, not to every line ever added.
Open this page on a laptop or desktop to write and run your code against the tests in the browser.
Spoilers: try each part before reading its walkthrough.
Treat parsing as validate first, convert second. For each line, skip it if line.strip() is empty. Otherwise call line.split() with no argument, which splits on any run of spaces or tabs and never produces empty fields. Then check the shape: exactly five fields, and the status and latency fields pass str.isdigit(). Only after those checks do you call int(), so a bad line can never raise an exception halfway through the file; it just bumps the malformed counter. Building the record as a plain dict keeps the output easy to print and compare. The whole pass is O(total characters) with O(1) work per line beyond the split, and it never needs to look back at earlier lines.
Group before you compute. One pass builds a dict from endpoint string (method + " " + path) to the list of its records; a small helper like _by_endpoint keeps this reusable for Part 3. Each statistic is then a short expression over one group: len for requests, a generator sum for status codes of 500 or more, sum(lat) // len(lat) for the floored average, and max for the slowest request. Sorting the result with the key (-requests, endpoint) gives descending counts with an alphabetical tie-break in a single sort. Grouping is O(n), the stats are O(n) in total across groups, and the final sort is O(E log E) for E endpoints. If memory mattered you would store running totals instead of lists.
Since every timestamp has the same fixed-width ISO layout, string order equals time order, so the window test is just start <= r["ts"] < end with no date parsing. Filter the records with that half-open condition, reuse the Part 2 grouping on the filtered list, and count each group. Sort by (-count, endpoint) and slice [:k]; slicing handles a k larger than the number of endpoints and a k of zero without special cases. This is O(n) to filter plus O(E log E) to rank. For repeated queries over a big log, sort records by timestamp once and use bisect to find the window edges in O(log n), or use a heap of size k when E is large and k is small.
A live stream means you can no longer filter a stored list on every question, so keep state that is updated as each line arrives. Reuse parse_logs([line]) to validate the line, convert its timestamp to whole seconds with datetime.fromisoformat, and push (seconds, endpoint) onto a deque. A dict holds the request count per endpoint for exactly the lines in the deque. After every accepted line, pop from the left while the oldest entry is window_seconds or more behind the newest, decrementing its count and deleting counts that hit zero. Because timestamps never go backwards, the deque stays sorted by time, so eviction only ever looks at the front. Each line is pushed and popped once, so add is amortised O(1), count is a dict lookup, and memory is bounded by the window.
Validate before converting: check the field count and str.isdigit() first, so int() can never raise on a bad line.
Group records into a dict from endpoint to list of records once; every stat is then a one-liner over a group, and Part 3 can reuse the grouping.
Sort with a key of (-requests, endpoint) to get descending counts with alphabetical tie-breaks in one pass.
Keep a deque of (seconds, endpoint) for accepted lines plus a dict of counts. After each add, pop from the left while the front is too old and decrement its count; delete a count when it reaches zero.
Parsing is linear in the input size. endpoint_stats groups in O(n) and sorts E endpoints in O(E log E). top_endpoints filters in O(n), then sorts the endpoints in the window. In LogMonitor each add is amortised O(1) because every line is evicted at most once, count is O(1), and top is O(E log E) for the E endpoints currently in the window.
The company guides collect the questions candidates report, with every part, the follow-ups interviewers added, and how often each one comes up.
Browse the interview guides