The Email Ranker
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
- L3
- Asked in
Problem
Each record in activities is one email event: a dict with a user_id and an action that is either send or receive, and both count toward a user's activity. Rank users by their total activity from most to least, settling ties alphabetically by user_id.
Some inboxes see more action.
Example 1
Input:
activities = [{"action":"send","user_id":"bob"},{"action":"send","user_id":"alice"},{"action":"receive","user_id":"alice"},{"action":"receive","user_id":"bob"},{"action":"send","user_id":"carol"},{"action":"send","user_id":"carol"},{"action":"receive","user_id":"carol"}]Output:
[{"rank":1,"user_id":"carol","activity_count":3},{"rank":2,"user_id":"alice","activity_count":2},{"rank":2,"user_id":"bob","activity_count":2}]Example 2
Input:
activities = [{"action":"send","user_id":"dave"},{"action":"send","user_id":"erin"},{"action":"receive","user_id":"dave"},{"action":"send","user_id":"frank"},{"action":"receive","user_id":"erin"},{"action":"send","user_id":"grace"},{"action":"send","user_id":"dave"},{"action":"send","user_id":"erin"},{"action":"receive","user_id":"frank"}]Output:
[{"rank":1,"user_id":"dave","activity_count":3},{"rank":1,"user_id":"erin","activity_count":3},{"rank":2,"user_id":"frank","activity_count":2},{"rank":3,"user_id":"grace","activity_count":1}]Worked solution and explanation
What this really is
This is a dense-ranked leaderboard hiding behind an email log. Anyone can count events and sort them from most to least; what actually separates candidates is the rank assignment. The trap is reaching for enumerate(ordered, 1) and calling the position a rank. Do that and two users with identical activity land on ranks 1 and 2 instead of both on rank 1, and every tie in the leaderboard reports a wrong, non-deterministic number.
Break down the requirements
Step 1: Count activities per user_id
One pass over activities builds {user_id: count}. The action field is irrelevant; both send and receive count, so only the total per user matters for ranking.
Step 2: Sort by count desc, then user_id asc
Use a composite key (-count, user_id). Negating the count flips the sort to descending while keeping the secondary sort on user_id ascending in a single sorted call.
Step 3: Assign DENSE rank, not row number
Dense rank means tied users share the same rank and the next distinct count gets rank + 1 (no gap). Increment rank only when the current count differs from the previous count, not on every row. The second example proves it: after two users tie at rank 1, the next lower total is rank 2, not rank 3.
Step 4: Shape each output row
Each output row is a dict with exactly the keys rank, user_id, and activity_count. Build the dict in that key order so it matches the expected rows.
The solution
def rank_by_email_activity(activities: list[dict]) -> list[dict]:
counts = {}
for a in activities:
uid = a['user_id']
counts[uid] = counts.get(uid, 0) + 1
ordered = sorted(counts.items(), key=lambda kv: (-kv[1], kv[0]))
result = []
rank = 0
prev_count = None
for uid, c in ordered:
if c != prev_count:
rank += 1
prev_count = c
result.append({'rank': rank, 'user_id': uid, 'activity_count': c})
return resultCommon follow-up questions
- What is the difference between RANK, `DENSE_RANK`, and `ROW_NUMBER`, and which one does this prompt ask for? (Tests SQL-window vocabulary. This prompt wants the flavor where ties share the same rank and the next group is rank + 1 with no gap.)
- How would you change the tie-breaker to most recent activity instead of alphabetical `user_id`? (Tests sort key extension. The candidate must carry a `last_seen` timestamp through the count step and sort by `(-count, -last_seen)`.)
- If `activities` arrived as a streaming feed of millions of events per minute, how would you maintain the leaderboard incrementally? (Tests online algorithms. Options include a heap of top K, a sorted structure keyed on count, or periodic re-sort of just the changed users.)