The Month-by-Month Snapshot
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
- L4
- Asked in
- JPMorgan Chase
Problem
An employee often logs several sales in the same month, so each month in our sales crosstab must hold that employee's combined total, keyed by the month's lowercased three-letter abbreviation. From the records, return one dict per employee with those month totals plus their employee_id, ordered alphabetically by employee_id.
Every salesperson has a story. The months just tell it sideways.
Example
Input:
records = [{"month":"January","employee_id":"E2","sales_amount":7000},{"month":"January","employee_id":"E1","sales_amount":5000},{"month":"February","employee_id":"E1","sales_amount":3000}]Output:
[{"jan":5000,"feb":3000,"employee_id":"E1"},{"jan":7000,"employee_id":"E2"}]Worked solution and explanation
This is a pivot with a duplicate-key trap dressed up as a sales report. Anyone can turn 'January' into jan. What separates candidates is whether each employee-month cell accumulates or overwrites. Write row[key] = record['sales_amount'] and the last record wins: an employee with two March sales of 1000 and 2500 reports 2500 instead of 3500. Nothing errors, and the report quietly loses real revenue.
Building it
Step 1: Key the totals by employee, then by month
The output has one dict per employee, so the natural working state is a dict of dicts: totals[employee_id][month_key]. setdefault(emp, {}) hands back the employee's inner dict, creating it on first sight, so the loop body never branches on whether the employee is new.
Step 2: Derive the month key from the data
The column names are not fixed in advance; they come from the values. record['month'][:3].lower() maps 'January' to 'jan' and 'September' to 'sep'. Keying on the full name would pass a quick eyeball check and fail every graded case.
Step 3: Accumulate, never assign
months.get(key, 0) + amount treats a missing month as zero and adds to an existing one. This one line is the whole difference between a correct crosstab and one that keeps only the last sale of each month.
Step 4: Order employees, not months
The records arrive in export order, and in the example E2 shows up before E1. sorted(totals) walks employees alphabetically by employee_id. Inside each dict only the keys and totals matter, so the months need no sort; employee_id is added after them to keep each row readable.
def monthly_sales_crosstab(records: list[dict]) -> list[dict]:
totals: dict[str, dict[str, int]] = {}
for record in records:
emp = record['employee_id']
key = record['month'][:3].lower()
amount = record['sales_amount']
months = totals.setdefault(emp, {})
months[key] = months.get(key, 0) + amount
result = []
for emp in sorted(totals):
row = totals[emp]
row['employee_id'] = emp
result.append(row)
return result- Overwrite
months[key] = amountkeeps whichever record came last. M3's two March sales (1000, then 2500) collapse to 2500, and the 1000 vanishes without a trace.- Accumulate
months[key] = months.get(key, 0) + amountfolds every record into its cell. M3's March reads 3500, and a month with a single sale is unaffected.
Common follow-up questions
- What if the months inside each dict had to appear in calendar order? (Tests mapping `jan`..`dec` to a month index and sorting by it.)
- How would you add a total column summing all months per employee? (Tests a second aggregate over the pivoted cells.)
- How would you write this crosstab in SQL? (Tests conditional aggregation with `SUM(CASE WHEN ...)` per month.)