DoorDash Key Metrics & Dashboard Requirements Questions
Defining and standardizing DoorDash KPIs, identifying data sources, calculating metric definitions, data governance, and designing dashboards and reporting pipelines to monitor product and business performance. Includes data visualization best practices, dashboard design, interactivity, drill-down capabilities, and alignment with business goals across operations, product, and marketplace analytics.
HardSystem Design
82 practiced
Design a scalable strategy to support 1000+ localized dashboards for DoorDash markets while keeping maintainability high. Describe templating approaches, parameterized dashboards, multi-tenant access control, caching and pre-aggregation, monitoring for stale dashboards, and CI processes to manage dashboard changes and prevent regressions.
Sample Answer
Requirements:- 1000+ localized dashboards (market-level) with consistent KPIs and local filters- Fast load times (<2s for common views), maintainability, auditability, secure market-level access- Use existing BI tool (e.g., Looker/Tableau/Power BI) and data warehouse (Snowflake/BigQuery)High-level approach:1. Templating & parameterization- Build a canonical dashboard template containing canonical tiles and parameterized filters (market_id, locale, currency, timezone).- Use embedded parameter expressions or templated SQL (LookML models, Tableau data source parameters, or Power BI templates) so visual logic is single-source.- Store localization strings and formatting rules in a metastore table referenced by templates.2. Multi-tenant access control- Implement row-level security (RLS) based on market_id tied to user roles/groups (SCIM/SSO sync from IAM).- For embedded/public views, generate short-lived access tokens and enforce tenant-scoped queries.3. Caching & pre-aggregation- Create aggregated materialized views by market and time grain (daily/weekly/hourly) in the warehouse using scheduled jobs.- Use BI tool extract caching or query-result cache with TTL; route heavy/complex tiles to pre-agg tables.- For rarely-used markets, generate on-demand cache warmers during business hours.4. Monitoring for staleness & freshness- Surface last_updated and row_count on dashboards; central freshness service polls materialized views and cache TTLs.- Alerting: SLA-based alerts (e.g., > X minutes stale) to Slack/email and auto-trigger recompute jobs.5. CI/CD & governance- Store dashboard templates and LookML/Tableau Workbooks in git. Use automated tests: - Lint template syntax and SQL - Data tests: validate key KPIs against known totals (unit tests) - Visual smoke tests: render sample market dashboards in headless mode to detect broken queries/filters- Use feature-branch deploys to a staging workspace; run test-suite and manual stakeholder sign-off before merge to prod.- Maintain migration scripts for schema changes with backward-compatible views.Trade-offs & maintenance- Pre-aggregations increase storage and pipeline complexity but yield predictable performance.- Centralized templates reduce duplication; allow controlled local overrides for market-specific tiles.- Invest in telemetry (query latency, cache hit rate, dashboard usage) to prune unused dashboards and optimize cost.Outcome- Single-source dashboard logic, secure market isolation, predictable performance via pre-agg + cache, and CI prevents regressions while enabling rapid rollout to 1000+ markets.
HardTechnical
115 practiced
You have a slow daily summary query that joins a 2-billion-row orders table with courier_profiles and geo_location tables. Propose detailed optimization strategies for Redshift and Snowflake: partitioning/clustering keys, sort keys vs clustering, materialized views, denormalization vs star schema, pre-aggregation, and cost estimates. Explain trade-offs between storage, ingestion complexity, and query speed.
Sample Answer
Situation: You’re running a slow daily summary that joins a 2B-row orders table with courier_profiles and geo_location. Below are concrete Redshift- and Snowflake-specific strategies, trade-offs, and rough cost considerations to help you choose.1) Clarify access patterns (must first)- Which columns are filtered (date range? courier_id? region?), join keys, and required aggregates. Optimize for the most frequent query shape.2) Redshift (OLAP columnar, sort & distribution keys)- Sort keys: use a compound or interleaved sort on the most selective filter (typically order_date for daily summary). Compound if queries always filter by date range then courier_id; interleaved if many different predicates.- Distribution keys: choose distkey = courier_id (or order_id shard) if joins are on courier_id to colocate rows; otherwise use EVEN and rely on proper sort keys.- Zone maps benefit when sort key matches filter. Vacuum/analyze regularly for large writes.- Materialized views: maintain pre-aggregated daily summaries (group by date, courier, region). Redshift MV refresh can be incremental if supported; schedule near-incremental refreshes to avoid full rebuilds.- Denormalization: store courier attributes (static) copied into orders as small lookup columns (e.g., courier_region_id) to avoid joining large courier_profiles on every run.- Pre-aggregation: maintain daily rollups in a summary table partitioned by date (time-partitioned) and update via upsert.- Trade-offs: storage modestly increases; ingestion complexity increases (ETL to populate summaries, VACUUM); query speed huge win. Cost: Redshift compute nodes cost depends on cluster size; storing pre-aggregates is cheap relative to compute.3) Snowflake (automatic clustering, micro-partitions)- Clustering key: define clustering on order_date ± courier_id or region to co-locate micro-partitions. Snowflake can auto-cluster (managed) but costs extra credits—use manual clustering key initially.- Materialized views: Snowflake MVs support automatic maintenance; good for frequently-run aggregates. Alternatively, create daily summary tables via tasks (scheduled SQL) for deterministic costs.- Denormalization: same advice—copy small courier attributes into orders at ingest to avoid joins.- Pre-aggregation & streams/tasks: use change data capture (streams) + tasks to incrementally maintain summary table for near-real-time ready reports.- Trade-offs: Snowflake storage cheaper and scales; auto-clustering & MV maintenance burn credits (compute). Materialized views are convenient but may be more costly than scheduled job + incremental upserts.4) Schema choices: star vs denormalized- Star schema: orders fact + small dimension tables (couriers, geo). Good for clarity, reduces redundancy, dimension updates easy. Joins still expensive at 2B rows.- Denormalized facts: duplicate small dim attributes into orders to eliminate joins at query time. Best for read-heavy summary queries. Trade-off: higher storage and more complex dimension updates on change.5) Additional performance tactics- Use predicate pushdown, filter as early as possible, avoid SELECT *.- Use result caching (Snowflake result cache, Redshift result cache/short-lived).- Parallelize ETL, use COPY optimally (Redshift compression encodings, sort-optimized files).- Run EXPLAIN/QUERY PROFILE and identify network/redistribution/scan hotspots.6) Cost estimates & guidance (very approximate)- Snowflake: extra compute for auto-clustering/MV might add 10–50% credit consumption for heavy maintenance; storing daily summaries adds negligible storage ($/TB-month small).- Redshift: larger cluster sizing to scan 2B rows costs more (on-demand ds nodes). Pre-aggregation can reduce nightly cluster-hours massively—pay once for ETL jobs.- Recommendation: measure current query cost (bytes scanned, execution time). If queries scan >90% of table, prioritize pre-aggregation/denormalization and clustering; if scans target small ranges, prioritize sort/clustering keys.7) Practical implementation plan- Step 1: profile query: identify top predicates and scanned columns.- Step 2: add minimal denormalized fields for frequent filters/joins.- Step 3: implement daily incremental summary table (streams/tasks or scheduled ETL) and test runtimes.- Step 4: set clustering/sort keys to align with filters; enable MV or materialized summary if query frequency justifies maintenance cost.- Step 5: monitor with cost/latency metrics and iterate.Bottom line: For a 2B-row fact, the biggest wins are pre-aggregation (materialized summary table) and reducing joins via denormalized small dims. Use sort/clustering to make selective scans cheap; choose Snowflake MVs/tasks for convenience at the cost of compute credits, or Redshift MV/rollup tables with careful vacuuming for lower ongoing compute but higher maintenance.
HardTechnical
83 practiced
Provide SQL or Python pseudocode to compute rolling retention for monthly cohorts with right-censoring. Output: cohort_month, month_n (0..N), active_users, cohort_size, retention_rate. Explain how you handle users with incomplete observation windows (censoring) and provide complexity considerations for large datasets.
Sample Answer
Approach: assign each user to a monthly cohort (first_seen truncated to month), compute months since cohort for each activity, and for each cohort-month N count users who were active in that month among those who were "at risk" (i.e., whose observation window extends at least to the end of that month). Right-censoring: exclude users from the denominator for month_n if their observation_end < cohort_month + n months.SQL (Postgres-like):Python (pandas pseudocode):Why censoring this way: a user should not be counted in the denominator for a month they were not observable (e.g., joined near dataset end or churned but telemetry missing). That prevents underestimating retention by dividing by full cohort size when some users couldn't possibly be observed.Complexity and scalability:- Input size: U users, E events, M months window.- Basic operations: grouping events by month — O(E) time, grouping users — O(U).- If expanding users into month rows (user × months), worst-case O(U*M) rows; avoid materializing by computing denominators via histogram aggregation (count users with max_n >= n) using bucketing or cumulative sums: - compute counts of users by cohort_month and max_n, then cumulative sum over max_n descending gives at_risk per month_n.- For very large datasets: pre-aggregate events to monthly unique user counts, partition by cohort_month, use distributed processing (Spark), and store intermediate aggregates. Indexes on user_id and event_date and partitioning by event_month/cohort_month improve performance. Use approximate methods (HyperLogLog) if memory constrained.
sql
-- Assumes users(user_id, first_seen, observation_end) and events(user_id, event_date)
WITH cohorts AS (
SELECT user_id,
date_trunc('month', first_seen)::date AS cohort_month,
observation_end::date AS observation_end
FROM users
),
events_month AS (
SELECT e.user_id,
date_trunc('month', e.event_date)::date AS event_month
FROM events e
JOIN cohorts c USING (user_id)
WHERE e.event_date >= c.cohort_month -- optional
GROUP BY 1,2
),
activity AS (
SELECT c.cohort_month,
em.event_month,
EXTRACT(YEAR FROM age(em.event_month, c.cohort_month)) * 12
+ EXTRACT(MONTH FROM age(em.event_month, c.cohort_month)) AS month_n,
em.user_id
FROM events_month em
JOIN cohorts c USING (user_id)
),
cohort_sizes AS (
SELECT cohort_month,
COUNT(*) AS cohort_size
FROM cohorts
GROUP BY cohort_month
),
at_risk AS (
-- for each cohort_month and month_n, count users whose observation_end >= cohort_month + month_n
SELECT c.cohort_month,
generate_series(0, 24) AS month_n -- N window (adjust)
FROM (SELECT DISTINCT cohort_month FROM cohorts) c
),
denom AS (
SELECT a.cohort_month, a.month_n, COUNT(DISTINCT c.user_id) AS at_risk_users
FROM cohorts c
JOIN at_risk a ON a.cohort_month = c.cohort_month
WHERE c.observation_end >= (a.cohort_month + (a.month_n || ' months')::interval)::date
GROUP BY 1,2
),
num AS (
SELECT cohort_month, month_n, COUNT(DISTINCT user_id) AS active_users
FROM activity
WHERE month_n >= 0
GROUP BY 1,2
)
SELECT d.cohort_month,
d.month_n,
COALESCE(n.active_users,0) AS active_users,
(SELECT cohort_size FROM cohort_sizes cs WHERE cs.cohort_month = d.cohort_month) AS cohort_size,
CASE WHEN d.at_risk_users = 0 THEN NULL
ELSE ROUND( (COALESCE(n.active_users,0)::numeric / d.at_risk_users)*100, 4)
END AS retention_rate_percent
FROM denom d
LEFT JOIN num n USING (cohort_month, month_n)
ORDER BY cohort_month, month_n;python
# users: user_id, first_seen (datetime), observation_end (datetime)
# events: user_id, event_date (datetime)
users['cohort_month'] = users['first_seen'].dt.to_period('M').dt.to_timestamp()
events['event_month'] = events['event_date'].dt.to_period('M').dt.to_timestamp()
# unique monthly activity
activity = events.drop_duplicates(['user_id','event_month']).merge(users[['user_id','cohort_month','observation_end']], on='user_id')
activity['month_n'] = ((activity['event_month'].dt.year - activity['cohort_month'].dt.year)*12 +
(activity['event_month'].dt.month - activity['cohort_month'].dt.month))
# denom: for each user, compute max_month_n_observable = floor(months between cohort_month and observation_end)
users['max_n'] = ((users['observation_end'].dt.year - users['cohort_month'].dt.year)*12 +
(users['observation_end'].dt.month - users['cohort_month'].dt.month))
# expand into rows per month_n up to max_n (use explode or efficient bin-aggregation)
# then groupby cohort_month, month_n to get at_risk_users; group activity to get active_usersMediumTechnical
79 practiced
Describe how you would compute customer Lifetime Value (LTV) for DoorDash using cohort analysis. Explain cohort definition (by acquisition week/month), revenue window selection, discounting future revenue, handling censored customers (partial observation), adjustments for promotions/refunds, and how to present uncertainty and assumptions to stakeholders.
Sample Answer
Approach summary: I’d compute LTV by doing cohort-based cumulative revenue-per-customer over a fixed observation horizon, then extrapolate beyond observed data with a parametric or nonparametric tail correction, adjusting for discounts, refunds, and censoring. Steps:Cohort definition- Define cohorts by acquisition week or month depending on granularity needed (weekly for fast-moving promos, monthly for executive trends). Cohort = users with first order in that period.Revenue window selection- Compute cumulative net revenue per cohort member at regular intervals (week 0,1,2... up to N weeks). Choose N so most activity is observed (e.g., 26–52 weeks) and stable.Discounting future revenue- Apply a business-appropriate discount rate (e.g., company WACC or MARR) to future-period revenues: discounted LTV = sum_t (mean revenue_t / (1+r)^(t/52)) for weekly buckets.Handling censored customers- Use survival / Kaplan–Meier or probabilistic churn models to estimate retention beyond observation and correct for right-censoring. For partial lifetimes, estimate expected remaining value conditional on survival curve.Adjustments for promotions/refunds- Use net revenue = gross order value − promotions − refunds − variable costs (commissions, delivery cost). Attribute promotion cost to the order where it occurred; optionally separate “organic LTV” vs “LTV including incentives” for comparisons.Modeling/extrapolation and uncertainty- Fit several tail models (e.g., exponential decay, Weibull, or ARIMA on cohort retention) and compare. Provide bootstrap confidence intervals around LTV and sensitivity to discount rate and tail choice. Report scenarios: conservative, base, optimistic.Presentation to stakeholders- Dashboard: cohort heatmap (cumulative revenue per user over time), LTV curves, and a scenario panel showing assumptions (discount rate, horizon, tail model) and CI bands. Call out key assumptions, data limitations (censoring window, promo-heavy cohorts), and actionables (e.g., CAC payback under each scenario).
MediumTechnical
66 practiced
Define an on-time delivery rate KPI for DoorDash where a delivery is on-time if delivered within ETA. Discuss implementation nuances: estimated ETA vs final ETA, clock skew between devices and servers, late status updates, timezone normalization, canceled or refunded orders, and approaches to backfill or exclude late-arriving events.
Sample Answer
KPI definition (concise):On-Time Delivery Rate = (Number of orders marked Delivered within ETA window) / (Total number of completed orders in measurement period). "Within ETA window" means the final delivered timestamp <= applicable ETA timestamp (see ETA selection rules below).Implementation nuances and recommended rules:- Estimated ETA vs Final ETA: - Use the ETA that was visible/committed to the customer at the time of tracking (customer-facing ETA). If ETA is updated after delivery, use the last ETA shown before the delivery event. Persist ETA change events with timestamps so report uses correct snapshot.- Clock skew between devices and servers: - Prefer server-generated timestamps for critical events (accepted, picked_up, delivered). If client timestamps are needed, normalize by measuring device-server offset during handshake and adjust. Store both original and normalized timestamps for audits.- Late status updates (e.g., courier forgets to mark delivered): - Use heuristics combining GPS telemetry and status events: if GPS shows courier at drop-off location and stop duration > X minutes, infer delivery time and flag as inferred. Mark inferred times separately and exclude/optionally include in primary KPI until validated.- Timezone normalization: - Store all timestamps in UTC and convert to business timezone only for presentation. When computing SLA windows (e.g., same-day promotions), convert ETA and delivered times into a consistent timezone before comparing.- Canceled/refunded orders: - Exclude cancellations before pickup from denominator. For refunds due to late delivery where refund policy explicitly removes the order from SLA, reflect business rule: either exclude or tag separately. Keep an audit column for exclusion reason.- Backfill and late-arriving events: - Implement an event ingestion window (e.g., 48–72 hours) during which late events can update KPI. After the window, freeze period metrics and surface a "late-arrival adjustments" incremental metric. Provide dashboards showing provisional vs finalized on-time rates.Data quality & reporting best practices:- Expose variant metrics: provisional (near real-time), final (post-backfill), and adjusted (excluding inferred deliveries).- Surface counts of inferred, corrected, and excluded orders so stakeholders understand impact.- Add confidence bands or annotations on dashboards when backfills materially change KPI.This approach balances accuracy, auditability, and operational usefulness for BI reporting.
Unlock Full Question Bank
Get access to hundreds of DoorDash Key Metrics & Dashboard Requirements interview questions and detailed answers.
Sign in to ContinueJoin thousands of developers preparing for their dream job.