The Status Board

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

In this batch of raw Nginx access logs, a line counts only when its quoted request names a method and a path and a three-digit status follows the closing quote; a request target with a raw space Nginx logged verbatim still counts, whatever trails the status (size, referer, user agent) doesn't matter, and every other line is junk to skip. The on-call engineer triaging the batch wants how many times each status code appears and every unique resource path that was hit, in the order the paths first show up. A path's query string is not part of its identity, so /api/users?page=1 and /api/users?page=2 both count as /api/users.

A pile of raw access logs, and two numbers that matter hiding inside the quotes.

Example

Input:

log_lines = ["10.0.0.1 - - [01/Jan/2024:00:00:00 +0000] \"GET /api/users?page=1 HTTP/1.1\" 200 512","10.0.0.2 - - [01/Jan/2024:00:00:01 +0000] \"GET /api/users?page=2 HTTP/1.1\" 200 512","10.0.0.1 - - [01/Jan/2024:00:00:02 +0000] \"POST /api/orders HTTP/1.1\" 201 128","10.0.0.3 - - [01/Jan/2024:00:00:03 +0000] \"GET /search?q=logs page HTTP/1.1\" 404 64","10.0.0.1 - - [01/Jan/2024:00:00:04 +0000] \"GET /health HTTP/1.1\" 200 2"]

Output:

[{"200":3,"201":1,"404":1},["/api/users","/api/orders","/search","/health"]]

Worked solution and explanation

What this problem is really testing

Beneath the log-parsing costume, this is field extraction anchored on quote delimiters plus order-preserving deduplication. The move everyone reaches for is fixed columns: line.split()[8] for the status, [6] for the path. It looks bulletproof against tidy sample data and it is, right up until a request field carries a raw space. A bot sends GET /search?q=logs page HTTP/1.1, Nginx logs the request line verbatim, and now every whitespace column after it is shoved one slot to the right. [8] is the protocol, not a status, so the line either crashes or gets silently dropped. The tell of seniority is seeing that the quotes are the only stable landmark in the line, and that a plain set would quietly scramble the first-seen path order the output demands.


Working through it

Step 1: Isolate the request field by its quotes, not by a token index

The request method, path, and protocol sit between double quotes, and the status code is the first token after the closing quote. Split the line on the quote character: index 1 is the request field, index 2 begins with the status. This holds even when the request contains extra spaces or when a referer and user-agent (also quoted) trail the line, because you are counting quotes, not spaces.

Step 2: Strip the query string from the path

Take the second token of the request field as the path, then split it on ? and keep the part before it. This is the trap the whole scenario is built around: /api/users?page=1 and /api/users?page=2 are the same resource, and skipping this step quietly multiplies your unique-path count.

Step 3: Count statuses and collect paths in first-seen order

Read the status as the first token after the closing quote and validate it is three digits, which also filters out truncated and junk lines. Tally statuses in a dict. Keep unique paths in a list, appending only on first sight, with a companion set for the membership test so the dedup stays O(1) per line while the list preserves order.

Step 4: Return the counts and the path list

Return a 2-element list: the status-counts dict followed by the list of unique paths. With no well-formed lines that is [{}, []].

Fixed-index split (fragile)
line.split()[8] for the status and line.split()[6] for the path assume the request is always exactly three tokens. The instant a request logs a raw space, the status moves to index 9 and this reads the protocol string as the status.
Quote-anchored split (robust)
parts = line.split(chr(34)) puts the request at parts[1] and the status at the head of parts[2]. The status is found relative to the closing quote, so internal request spaces and trailing quoted fields cannot move it.

The solution

Quote-anchored parsing with dict counts and order-preserving dedup
def analyze_logs(log_lines: list) -> list:
    status_counts = {}
    unique_paths = []
    seen = set()
    for line in log_lines:
        parts = line.split('"')
        if len(parts) < 3:
            continue
        request = parts[1].split()
        after = parts[2].split()
        if len(request) < 2 or not after:
            continue
        status = after[0]
        if len(status) != 3 or not status.isdigit():
            continue
        path = request[1].split("?")[0]
        status_counts[status] = status_counts.get(status, 0) + 1
        if path not in seen:
            seen.add(path)
            unique_paths.append(path)
    return [status_counts, unique_paths]

Common follow-up questions