← Boston Consulting Group Interview Insights

Boston Consulting Group·Data Scientist·Take-home Assignment·Senior

SeniorPrefer not to say
Jul 2026Remote

Summary

BCG data scientist take-home that was basically one enormous pandas question disguised as five smaller ones. The scope was way bigger than I expected for a single problem, and I spent way too long on the dedup and timezone normalization pieces before I even got to the feature engineering.

Questions Asked (5)

Q1

Given two CSVs with transaction and user data, write production-ready pandas code (no groupby.apply, no row-level loops, must scale to 100M+ rows) to: parse and UTC-normalize timestamps, deduplicate transactions by keeping the row with the latest updated_at per txn_id, and drop rows with invalid amounts, invalid transaction types, or timestamps outside a defined range.

Data ModelingTechnical Trade-offs
Author's notes

The dedup part sounds trivial until you realize there are near-duplicate rows sharing the same txn_id and dup_hint but different updated_at values.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

Start by clarifying the data schema, timestamp formats, and validation rules, then outline a vectorized pandas pipeline that uses merge, sort_values, drop_duplicates, and boolean masks instead of apply or loops. Emphasize scalability by discussing chunking, dtype optimization, and memory-efficient operations for 100M+ rows.

Pro tip: Mention that you would validate the pipeline on a small sample and use pandas' nullable dtypes and categorical types to reduce memory, and consider using Dask or Polars if pandas becomes a bottleneck.

1. Clarify requirements and data schema

Ask about CSV structure, timestamp formats, timezone info, valid transaction types, amount ranges, and the defined timestamp range. Confirm whether user data is needed for joins or just for reference.

2. Design a vectorized pipeline

Outline steps: read CSVs with appropriate dtypes, parse timestamps to UTC, merge user data if needed, deduplicate by sorting and drop_duplicates, and apply boolean masks for validation. Avoid apply and loops.

3. Implement deduplication and validation

Use sort_values(['txn_id', 'updated_at'], ascending=[True, False]) followed by drop_duplicates('txn_id', keep='first') to keep latest. Create masks for invalid amounts, types, and timestamps using vectorized comparisons.

4. Optimize for scale

Discuss chunked reading, dtype downcasting (e.g., float32, category), and using efficient file formats like Parquet. Mention alternatives like Dask or Polars for out-of-core processing.

5. Validate and test

Describe how to test on a small sample, check for edge cases (e.g., NaT, duplicates with same updated_at), and ensure the output meets business rules.

Key Points to Mention

  • Vectorized operations: merge, sort_values, drop_duplicates, boolean indexing
  • Timestamp normalization: pd.to_datetime with utc=True, handling mixed formats
  • Deduplication strategy: sort by updated_at descending and keep first per txn_id
  • Validation masks: using .between, .isin, and .notna for amounts, types, and timestamps
  • Scalability: chunking, dtype optimization, and considering Dask/Polars for 100M+ rows
  • Production readiness: logging, error handling, and testing on sample data

AI-generated suggestions, not part of the candidate's original notes. May be inaccurate — verify before relying on them.

Q2

Separate refunds and chargebacks from purchase transactions, define net_spend as the sum of purchase amounts only, and retain a flag indicating whether any refunds or chargebacks exist per user.

Product Analytics & MetricsData Modeling
Author's notes

Pretty mechanical once the dedup is done.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

Start by clarifying the transaction data schema and business definitions of refunds and chargebacks. Then design a query that aggregates purchase amounts separately while using conditional logic to flag any refund or chargeback activity per user. Finally, validate the results and discuss edge cases like multiple refunds or partial refunds.

Pro tip: Mention that refunds and chargebacks should be excluded from net_spend but their presence is still valuable for segmentation, and consider using a LEFT JOIN or window function to efficiently compute the flag without duplicating rows.

1. Clarify data model and definitions

Confirm the transaction table structure, how refunds and chargebacks are recorded (e.g., negative amounts, separate type column), and the time window for analysis.

2. Separate purchase transactions

Filter the transaction data to include only purchase records, ensuring refunds and chargebacks are excluded from the net_spend calculation.

3. Compute net_spend per user

Aggregate the purchase amounts by user to calculate net_spend as the sum of purchase amounts only.

4. Create refund/chargeback flag

Use a conditional aggregation or window function to set a flag (e.g., 1/0) indicating whether the user has any refund or chargeback transactions.

5. Validate and handle edge cases

Check for users with only refunds/chargebacks (net_spend = 0), partial refunds, and ensure the flag correctly captures any occurrence.

Key Points to Mention

  • Definition of net_spend: sum of purchase amounts only, excluding refunds and chargebacks.
  • Importance of a flag to identify users with refunds or chargebacks for further analysis.
  • Use of conditional aggregation (e.g., CASE WHEN) to compute the flag without affecting net_spend.
  • Handling of users with no purchases but refunds/chargebacks (net_spend should be 0 or NULL).
  • Efficiency considerations: avoid self-joins or subqueries that may impact performance on large datasets.
  • Potential business implications: refunds/chargebacks may indicate customer dissatisfaction or fraud, useful for segmentation.

AI-generated suggestions, not part of the candidate's original notes. May be inaccurate — verify before relying on them.

Q3

Build month-level features per user for a five-month window, where months are defined in the user's local timezone but aggregated after converting to UTC. Features include: active_month (binary), monthly_net_spend, and a rolling 3-month median spend computed only over active months (no zero-filling for inactive months).

Data ModelingTechnical Trade-offsProduct Analytics & Metrics
Author's notes

This is where I burned the most time.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

Start by clarifying the data schema and timezone handling, then outline a pipeline that first assigns each transaction to a local month, converts timestamps to UTC for aggregation, and computes features per user-month. Emphasize the rolling median calculation over active months only, and discuss trade-offs between window functions and self-joins.

Pro tip: Mention that you would validate the timezone conversion by checking edge cases like transactions near month boundaries and users in different timezones, and consider using a calendar table to ensure all user-month combinations are represented before filtering to active months.

1. Clarify requirements and data schema

Confirm the definition of 'active month' (e.g., at least one transaction), the five-month window (e.g., last five complete months), and the source of user timezone. Identify the transaction table with user_id, timestamp, amount, and timezone.

2. Assign local months and convert to UTC

For each transaction, convert the timestamp to the user's local timezone to extract the local month, then convert the timestamp to UTC for aggregation. Ensure consistent handling of daylight saving time if applicable.

3. Aggregate monthly spend per user

Group by user_id and local month to compute total net spend (sum of amounts) and determine active_month (1 if any transaction, else 0). Note that inactive months will have no rows, so they must be handled later.

4. Compute rolling 3-month median over active months

For each user, consider only months where active_month=1. For each active month, compute the median of monthly_net_spend over the current and previous two active months (if available). Use window functions or a self-join to achieve this without zero-filling.

5. Validate and handle edge cases

Check for users with fewer than three active months, missing months, and timezone edge cases. Ensure the output includes all user-month combinations for the five-month window, with inactive months having active_month=0 and null or zero spend as appropriate.

Key Points to Mention

  • Timezone conversion: use user's local timezone to define months, but store/aggregate in UTC to avoid ambiguity.
  • Active month definition: binary flag based on whether the user had any transaction in that local month.
  • Rolling median: computed only over active months, ignoring inactive months (no zero-filling).
  • Window functions: use ROWS BETWEEN 2 PRECEDING AND CURRENT ROW on a filtered set of active months, or a self-join to find the previous two active months.
  • Handling missing months: generate a complete user-month grid (e.g., with a calendar table) and left join aggregates to it.
  • Performance considerations: partitioning by user and ordering by month for window functions; indexing on user_id and timestamp.

AI-generated suggestions, not part of the candidate's original notes. May be inaccurate — verify before relying on them.

Q4

For each user-month, identify the top 3 merchant categories by net spend, with ties broken first by spend then lexicographically by category name, and output them as cat1, cat2, cat3 columns. Fill with 'None' if fewer than 3 categories exist.

Data ModelingAlgorithms & Data Structures
Author's notes

I used nlargest with a sort_values fallback for the lexicographic tiebreak and then pivoted.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

Start by aggregating net spend per user-month-category, then rank categories within each group using a deterministic ordering: descending net spend, then ascending category name. Finally, pivot the top three into cat1, cat2, cat3 columns, filling missing slots with 'None'.

Pro tip: Explicitly state how you handle ties and missing categories, and mention that you would validate the output with edge cases like users with zero or one category to ensure robustness.

1. Aggregate net spend

Compute net spend per user, month, and merchant category by summing transaction amounts (or net of refunds).

2. Rank categories

Within each user-month, rank categories by net spend descending, then by category name ascending to break ties.

3. Select top 3

Filter to the top three ranked categories per user-month, ensuring you capture the correct order.

4. Pivot to columns

Transform the ranked list into cat1, cat2, cat3 columns, using 'None' for any missing positions.

5. Validate output

Check for correctness by testing edge cases (e.g., users with fewer than 3 categories) and verifying tie-breaking logic.

Key Points to Mention

  • Use of window functions (e.g., ROW_NUMBER or RANK) with PARTITION BY user, month and ORDER BY net_spend DESC, category ASC.
  • Handling ties: the lexicographic tie-breaker ensures deterministic ordering.
  • Filling missing categories with 'None' using conditional logic or COALESCE.
  • Efficiency considerations: indexing, partitioning, and avoiding unnecessary shuffles in distributed systems.
  • Edge cases: users with no transactions, months with no activity, and categories with zero net spend.
  • Validation: comparing results with a manual check or using a smaller dataset to verify correctness.

AI-generated suggestions, not part of the candidate's original notes. May be inaccurate — verify before relying on them.

Q5

Produce a single-row-per-user snapshot as of 2024-07-31 with columns for months active in the last 5 months, total net spend, a refund/chargeback flag, rolling 3-month median spend at snapshot date, and the top-3 merchant categories for July 2024. The pipeline must be idempotent and memory-efficient.

System DesignTechnical Trade-offs
Author's notes

The idempotency requirement is mostly about making sure your dedup and sort logic is deterministic, which it is if you sort by updated_at and break ties consistently.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

Start by clarifying the requirements and assumptions, then design a modular pipeline that processes data incrementally and computes each metric efficiently. Focus on idempotency by using deterministic transformations and overwrite/merge strategies, and on memory efficiency by leveraging window functions and partitioning. Finally, discuss trade-offs and validation steps.

Pro tip: Emphasize partitioning by user and date to enable incremental processing, and use approximate algorithms (e.g., t-digest) for median calculations when exactness isn't critical. Also, mention the importance of handling late-arriving data and timezone consistency.

1. Clarify Requirements and Assumptions

Ask questions to confirm definitions: what counts as 'active'? How is net spend calculated (refunds subtracted)? What defines a refund/chargeback flag? Confirm snapshot date and timezone.

2. Design Data Model and Pipeline Architecture

Outline a star schema with fact tables for transactions and refunds, and dimension tables for users and merchants. Propose a batch pipeline with incremental processing, partitioning by date and user.

3. Compute Metrics Efficiently

Use window functions for rolling calculations (e.g., months active, rolling median) and aggregations for net spend and top categories. For median, consider approximate algorithms if data volume is large.

4. Ensure Idempotency and Memory Efficiency

Implement idempotency via deterministic transformations and overwrite/merge on primary keys. Optimize memory by processing in chunks, using columnar storage, and avoiding unnecessary shuffles.

5. Validate and Monitor

Define data quality checks (e.g., counts, nulls, ranges) and compare against expected results. Set up monitoring for pipeline failures and performance metrics.

Key Points to Mention

  • Use of window functions for rolling metrics (e.g., SUM OVER, MEDIAN OVER)
  • Partitioning and bucketing strategies for scalability
  • Idempotency through deterministic ETL and upsert/merge patterns
  • Memory efficiency via columnar formats (Parquet), predicate pushdown, and avoiding cross joins
  • Handling of late-arriving data and timezone normalization
  • Trade-offs between exact and approximate median calculations

AI-generated suggestions, not part of the candidate's original notes. May be inaccurate — verify before relying on them.