The coding question
Build a rating system for customer support agents. Record ratings as they come in, and return every agent with their average rating, ordered from highest to lowest.
Ask a few questions first
| Ask | What we'll assume |
|---|---|
| What does a rating look like? | A whole number from 1 to 5. Anything else is rejected with an error. |
| How should ties be ordered? | More ratings first, since that average is more trustworthy, then by name so the order is always the same. |
| How precise should averages be? | Round to two decimal places in the output. Keep exact sums inside. |
| Are writes or reads more common? | Lots of ratings come in, and the ranked list is asked for now and then. So recording should be cheap. |
| What about agents with no ratings? | They're not in the list until they get their first one. |
A couple of examples
| Input | Output |
|---|---|
| record ana 5, ben 3, ana 4, cy 5, ben 4. Then ranked() | [(cy, 5.0), (ana, 4.5), (ben, 3.5)] |
| record dee 4, eli 5, eli 3, fay 4. Then ranked() | [(eli, 4.0), (dee, 4.0), (fay, 4.0)]. All tied, eli has more ratings, then names |
| record g 1, g 2, g 2. Then ranked() | [(g, 1.67)] |
Start with brute force
Store every rating in a list. When asked for the ranking, group the list by agent, average each group, and sort.
Cost: recording is O(1), but every ranking call is O(r + a log a) for r ratings and a agents, and you keep every rating forever.
A better way
An average only needs two numbers: the sum and the count. So keep a hash map from agent to (sum, count) and update it on every rating. You never need the full list.
Ranking is then just sorting the agents, which are far fewer than the ratings. Sorting with a key of (negative average, negative count, name) gives highest first, then more ratings, then alphabetical, in one sort.
- On record, check the rating is 1 to 5.
- Add it to the agent's sum and add one to their count, creating the entry if it's new.
- On ranked, build (agent, average, count) for every agent.
- Sort by highest average, then most ratings, then name.
- Return each agent with their average rounded to two places.
The code
class AgentRatings:
def __init__(self):
self.totals = {} # agent -> [sum of ratings, number of ratings]
def record(self, agent, rating):
if rating not in (1, 2, 3, 4, 5):
raise ValueError("rating must be a whole number from 1 to 5")
total = self.totals.setdefault(agent, [0, 0])
total[0] += rating
total[1] += 1
def ranked(self):
"""Every agent with their average, highest first.
Ties go to the agent with more ratings, then by name."""
rows = [(agent, s / n, n) for agent, (s, n) in self.totals.items()]
rows.sort(key=lambda row: (-row[1], -row[2], row[0]))
return [(agent, round(average, 2)) for agent, average, _ in rows]| Cost | Why | |
|---|---|---|
| Record | O(1) | One dictionary update |
| Ranked | O(a log a) | Sorting a agents |
| Space | O(a) | Two numbers per agent, no matter how many ratings |
Test it
Walk through one of the examples out loud, then check the cases that tend to break things:
- No ratings yet: return an empty list.
- Everyone tied: the order falls back to rating count, then name, so it's stable.
- A rating of 0, 6 or 4.5: rejected with an error.
- An average like 5/3: shown as 1.67, but the exact sum and count are kept so later ratings stay accurate.
Common mistakes
- Storing a running average and updating it with rounding each time. Errors build up. Keep the sum and count instead.
- Sorting by average only, so ties come back in a different order each run.
- Rounding before sorting, which turns 4.666 and 4.664 into a tie that wasn't there.
- Dividing with integer division in a language that does that, which floors every average.
Follow-up questions
What if you only want the top 10, with millions of agents?
Use a heap of size 10 while going through the agents. That's O(a log 10) instead of sorting everything. If it's asked for constantly, keep agents in a sorted structure and update one entry per rating.
What if they want averages per month?
Pass a timestamp in with each rating, and key the totals by (agent, month) as well. A month's ranking only looks at that month's entries. For 'last 30 days', keep daily totals and add up the last 30.
How would you export the results?
Add a method that writes ranked() out as CSV rows: agent, average and count. Keep formatting separate from the class that does the math, so adding JSON later doesn't touch it.
How the time adds up
| Minutes | What you're doing |
|---|---|
| 0–5 | Ask about rating values, ties and rounding |
| 5–10 | Brute force: keep every rating and group on demand |
| 10–20 | Keep sums and counts, and pick the sort order |
| 20–35 | Write the code |
| 35–40 | Test ties, empty input and bad ratings |
| 40–45 | Follow-up questions about top k, time periods and export |
Key concepts used here
- Hash maps and sliding windowsSwap nested loops for a single pass.
- Heaps and priority queuesAlways have the smallest or largest item ready.
- Object-oriented designSplit a small program into classes that are easy to change.
We wrote this sample solution ourselves. Practice Loop is independent and isn't affiliated with or endorsed by Atlassian.