# Above the Title

> Some names carry a genre. Total the box office behind them.

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

Domain: Python · Difficulty: medium · Seniority: L4

## Problem

You have three exports from a film catalog: `movies` (each with a `movie_id`, `genre`, `rating`, and `box_office`), `actors` (each with an `actor_id` and `name`), and `mapping`, the bridge rows that pair an `actor_id` with a `movie_id`. For a given `genre` and `min_rating`, return each actor's `name` mapped to the total `box_office` of the films they appeared in whose genre matches and whose rating is at least `min_rating`. Leave out any actor with no qualifying film.

## Worked solution and explanation

### What this really is

Strip the movie costume and this is a filtered join across two dimension tables and a bridge, collapsed to one total per surviving actor. The thing being probed is whether you reach for lookup dicts the moment data arrives as flat lists, or whether you fall into the trap of scanning movies inside the actor loop. Anyone can get the right numbers; the people who get hired turn every repeated search into an O(1) dict read before they write the aggregation.

---

### How to get there

#### Step 1: Index the actors

Build `actors_by_id = {a['actor_id']: a['name'] for a in actors}`. That one O(A) pass turns the bridge's `actor_id` into a name in constant time later, instead of re-scanning the actors list per mapping row.

#### Step 2: Filter the movies up front

Pre-compute `eligible = {m['movie_id']: m['box_office'] for m in movies if m['genre'] == genre and m['rating'] >= min_rating}`. Evaluating the predicate once per movie (not once per bridge row) is the difference between O(M) and O(M*K). Note `>=`: a rating exactly equal to `min_rating` qualifies.

#### Step 3: Aggregate in a single pass

Walk `mapping` once, skip any row whose `movie_id` is not in `eligible`, and add the stored box_office into a `defaultdict(int)` keyed by name. Casting to `dict()` at the end gives a plain dict and, crucially, never inserts a key for an actor who contributed nothing.

---

### The solution

**Index, filter, aggregate**

```python
from collections import defaultdict

def actor_genre_box_office(movies: list[dict], actors: list[dict], mapping: list[dict], genre: str, min_rating: float) -> dict:
    actors_by_id = {a['actor_id']: a['name'] for a in actors}
    eligible = {
        m['movie_id']: m['box_office']
        for m in movies
        if m['genre'] == genre and m['rating'] >= min_rating
    }
    totals = defaultdict(int)
    for row in mapping:
        movie_id = row['movie_id']
        if movie_id not in eligible:
            continue
        actor_name = actors_by_id.get(row['actor_id'])
        if actor_name is None:
            continue
        totals[actor_name] += eligible[movie_id]
    return dict(totals)
```

> **Cost analysis**
>
> Time is O(M + A + K): one pass each over movies, actors, and the bridge. Space is O(M + A) for the lookups plus O(R) for the R qualifying actors. The naive version that re-scans movies inside the mapping loop is O(M*K) and falls over on a real catalog.

> **Interviewers watch for**
>
> The tell is whether you build the lookups before iterating, whether the genre/rating predicate runs once per movie instead of once per bridge row, and whether actors with zero qualifying films are absent from the output rather than present with a 0.

> **Common pitfall**
>
> Seeding `totals` with every actor at 0 (or iterating actors and defaulting missing sums to 0) silently violates the 'at least one qualifying film' rule. Let the defaultdict create a key only when you actually add revenue, and the empty-result case handles itself.

---

## Common follow-up questions

- How would you return the result ordered by total box_office, highest first? _(Wrap with dict(sorted(totals.items(), key=lambda kv: -kv[1])). Worth discussing that insertion order is preserved since Python 3.7, so the sorted order survives the cast.)_
- What if two actors share the same name but have different ids? _(Key the totals by actor_id and only translate to name in the final projection, otherwise two different people sharing a name get their revenue merged.)_
- How would this look in pandas, and when is that the wrong tool? _(Three DataFrames, two merge calls, then groupby('name')['box_office'].sum(). Compare readability and memory against the dict approach on a large catalog.)_

## Related

- [All practice problems](https://datadriven.io/problems)
- [Mock interview mode](https://datadriven.io/interview/above_the_title)
- [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.