# Sum of Their Parts

> A stream of raw actions, folded into one portrait per person.

Canonical URL: <https://datadriven.io/problems/sum_of_their_parts>

Domain: Python · Difficulty: medium · Seniority: L4

## Problem

You're collapsing a raw event stream into one record per user, where each event carries a `user_id`, an `event_type`, and an `amount`. For every user, total their amounts within each `event_type` so each distinct type becomes one key on that user's record, keeping only the types that user actually triggered (absent types stay absent, not zero). Order each record's `event_type` keys from highest total to lowest with `user_id` last, and return the records sorted by `user_id` ascending.

## Worked solution and explanation

### What this really is

Strip off the 'pivot' costume and this is a two-level accumulation: a sum bucketed by (user, `event_type`), then reshaped into one row per user. Everyone gets the outer grouping right. What separates candidates is the two details hiding in the output shape: the columns are *sparse* (a user only carries the types they actually triggered, never a zero-filled grid) and they are *ordered by their own total*, descending, with `user_id` pinned last. Miss either and your rows look almost right but fail equality.

---

### How to get there

#### Step 1: Accumulate into user -> {type: total}

Walk the events once into a nested dict keyed by `user_id`, whose value is another dict mapping `event_type` to its running total. One pass, no pre-scan of the type universe. This is what keeps the pivot sparse: a type only appears in a user's inner dict if that user produced it.

#### Step 2: Add, never overwrite

Use `dict.get(etype, 0) + amount` so the second click for the same user folds into the first instead of clobbering it. The single most common wrong answer assigns instead of adds and silently keeps only the last amount.

#### Step 3: Reshape in the required order

Iterate users in `sorted` order for the outer list. For each user, emit the type keys sorted by descending total, then append `user_id` last. Insertion order into the dict IS the column order you are graded on.

---

### The solution

**One-pass accumulate, then ordered reshape**

```python
def pivot_events(events):
    users = {}
    for event in events:
        uid = event['user_id']
        etype = event['event_type']
        bucket = users.setdefault(uid, {})
        bucket[etype] = bucket.get(etype, 0) + event['amount']
    result = []
    for uid in sorted(users):
        sums = users[uid]
        row = {etype: sums[etype] for etype in sorted(sums, key=lambda e: -sums[e])}
        row['user_id'] = uid
        result.append(row)
    return result
```

> **Time and space**
>
> **Time:** O(n + u * k log k): n events for the single pass, then per user a sort of that user's k distinct types. Space: O(n) for the nested accumulation. There is no second pass over the data and no materialization of a dense user-by-type matrix, which is what would blow up memory at scale.

> **Interviewers watch for**
>
> The tell is whether you produce a dense grid or a sparse one. A candidate who pre-collects every `event_type` and zero-fills each user is solving a different problem: the spec says missing types are absent, not zero. Watch also for the ordering being applied to the wrong axis: it's per-row by that row's totals, not a global column order.

> **Common pitfall**
>
> Assigning `bucket[etype] = amount` instead of accumulating. With two same-type events for one user it keeps only the last, and the bug is invisible until a user happens to repeat a type.

---

## Common follow-up questions

- Now make it a dense pivot where every user carries every `event_type`, defaulting to 0. _(Tests collecting the full type universe first and initializing each user's row with zeros.)_
- How would you express this with pandas instead? _(Tests knowledge of `pd.pivot_table` with index, columns, values, and aggfunc.)_
- What changes if there are billions of events across millions of users? _(Tests streaming or chunked accumulation when the event list does not fit in memory.)_
- How would you break ties when two `event_types` have equal totals? _(Tests defining a deterministic secondary sort key.)_

## Related

- [All practice problems](https://datadriven.io/problems)
- [Mock interview mode](https://datadriven.io/interview/sum_of_their_parts)
- [Python Interview Questions](https://datadriven.io/python-interview-questions)
- [Data Engineering Interview Prep Guide](https://datadriven.io/data-engineer-interview-prep)
- [Daily Challenge](https://datadriven.io/daily)

---

Source: DataDriven (https://datadriven.io). DataDriven is the data engineering interview community. Live code execution in SQL, Python, and Spark sandboxes. Every feature is open to every member.