The Log Pulse
An easy Python interview practice problem on DataDriven. Write the Python and run it against test cases, with instant feedback.
- Domain
- Python
- Difficulty
- easy
- Seniority
- L3
- Asked in
- Amazon
Problem
Lines from the monitoring pipeline are shaped like [LEVEL] message, but some arrive empty, missing their brackets, or otherwise garbled, and the on-call engineer reviewing a batch files every one of those under 'UNKNOWN'. Report how many times each level appears alongside the most frequent level, giving a tie to whichever level shows up first in log_lines; an empty batch comes back as [{}, ''].
Some lines repeat themselves.
Example 1
Input:
log_lines = ["[ERROR] Connection timeout","[INFO] Server started","[ERROR] Disk full","[WARN] High memory","[INFO] Request handled","[INFO] Cache hit"]
Output:
[{"ERROR":2,"INFO":3,"WARN":1},"INFO"]Example 2
Input:
log_lines = ["[INFO] startup","[ERROR] failure","[ERROR] crash","[INFO] request"]
Output:
[{"INFO":2,"ERROR":2},"INFO"]Worked solution and explanation
Why this problem exists in real interviews
This looks like a counting exercise, and the counting is the easy half. The real test is the two things the happy path hides: malformed lines that crash a careless parser, and a tie rule where the level seen first wins. Anchor on the wrong character and a bracket-less line throws IndexError. Sort the levels alphabetically before picking the max and a batch where INFO showed up first hands the tie to ERROR. Either way you confidently report the wrong level on data that looked clean.
Break down the requirements
Step 1: Extract the level token from each line
Each line looks like '[LEVEL] message'. Find the first ], insist the line opens with [, and strip the padding between them so '[ DEBUG ]' counts as DEBUG. Keying on the first ] keeps '[INFO] [retry] reconnect' an INFO line. Anything that fails (no ], no leading [, an empty '[]') goes to 'UNKNOWN' instead of crashing or vanishing. A naive split that takes token zero skips this guard, and the dirty batch punishes exactly that.
Step 2: Tally with Counter
collections.Counter is built for this: one pass to build, constant-time increments, and a most_common helper. Indexing a missing key returns 0, so counts[level] += 1 needs no setup.
Step 3: Pick the most common level deterministically
Counter.most_common(1) returns the highest-count item, and ties keep insertion order, so the level that appeared first in the batch wins. That matches the rule. Note it is first appearance, not first to reach the top count: in '[INFO], [ERROR], [ERROR], [INFO]' ERROR hits two first, yet INFO wins. Re-sorting the keys before taking the max throws that order away.
Step 4: Return a fixed-shape list, not a tuple
The result is a fixed-shape list [counts_dict, most_common_level]. Return a plain list (not a tuple) and a plain dict (not a Counter), with the empty batch short-circuiting to [{}, ''] before most_common is touched.
The solution
from collections import Counter
def count_log_levels(log_lines: list[str]) -> list:
counts = Counter()
for line in log_lines:
try:
end = line.index(']')
if not line.startswith('['):
raise ValueError
level = line[1:end].strip()
if not level:
raise ValueError
except (ValueError, AttributeError):
level = 'UNKNOWN'
counts[level] += 1
if not counts:
return [{}, '']
most_common_level = counts.most_common(1)[0][0]
return [dict(counts), most_common_level]Common follow-up questions
- How would you break ties alphabetically instead of by first appearance? (Take `min` over `counts.items()` with `key=lambda kv: (-kv[1], kv[0])`.)
- What changes if the log format puts a timestamp before the bracket? (A regex anchored on the bracket like `\[(\w+)\]`, or stripping a fixed-width timestamp first.)
- How would you scale this to a 1 TB log file? (Stream line by line and keep the small tally in memory, or shard by level hash across workers and merge the counts.)