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
Google

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

Count, composite sort, dense rank assignment
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 result

Common follow-up questions