Business Intelligence, Reporting, and Dashboards Questions
The reporting and presentation layer of analytics: semantic/metrics layers, report development and automation, self-service BI, and the architecture that feeds dashboards and reports. Covers dashboard and visualization design (tool selection across Tableau/Power BI/Looker-style platforms, drill-downs, information architecture, communicating metrics visually), refresh strategies, and query performance for interactive reporting workloads. Spans both the engineering behind the reporting layer and the design of the dashboards that consume it.
Design monitoring and alerting for a fleet of scheduled reports: what would actually tell you a report failed, went stale, or started producing suspicious numbers before a stakeholder notices, and how do you route and escalate those alerts without drowning the on-call person in noise?
Sample Answer
Direct answer
Monitoring and alerting for a fleet of scheduled reports needs to catch three distinct kinds of problem: the job didn't run or failed outright, the data it produced is stale, and the data it produced looks statistically wrong even though the job technically succeeded, each of which needs a different signal and a different alert, routed and prioritized so the on-call person isn't drowning in low-value noise.
Structured elaboration
What to actually collect: job success rate and runtime (a job that succeeds but takes three times longer than usual is an early warning before it starts failing outright), freshness latency (how far behind is the data this report is showing, measured against when it should have refreshed), and a drift or anomaly score on the key output values themselves (did this report's headline number move far outside its normal range compared to recent history).
Alert thresholds: static thresholds work for some things (job failed = alert, full stop) but freshness and drift usually need a range calibrated to the report's own normal variability, not a single global number; a report that normally updates every 15 minutes breaching a 1-hour freshness threshold is a real problem, while a monthly report being '2 hours late' relative to a naive global threshold is meaningless noise.
Routing and escalation: not every alert should page the same person at the same urgency; a hard failure on a widely-viewed executive report escalates fast, while a freshness warning on a low-traffic internal report can go to a queue someone checks during business hours rather than waking anyone up.
Minimizing false positives: an alerting system that cries wolf trains people to ignore it, which is worse than having no alerting at all, so thresholds need periodic tuning against actual incident history (did this alert fire and turn out to matter, or fire and turn out to be nothing) rather than being set once and left alone.
Worked example
An enterprise BI (business intelligence) platform runs 40 scheduled reports with varying cadences. The monitoring setup: every job emits success/failure, runtime, and row-delta-versus-yesterday to a central monitoring table; a job failure on any report triggers an immediate alert, routed by report criticality (the 5 executive-tier reports page the on-call engineer immediately; the other 35 go to a shared Slack channel reviewed each morning); freshness thresholds are set per report based on its own expected cadence (a 15-minute report alerts if it's more than 45 minutes stale, a daily report alerts if it's more than 3 hours late relative to its usual completion time); and a row-count-delta check flags any report whose output row count differs by more than 50% from the prior run, which caught a real incident where an upstream filter change silently dropped most of a report's rows even though the job itself completed successfully with no error.
Trade-offs and pitfalls
Too many alerts at uniform urgency is the single most common failure of a monitoring setup like this: if every one of 40 reports pages the same on-call person at 2 AM for a minor freshness blip, people start ignoring pages, which means the ONE alert that actually mattered gets missed too, so tiering alerts by actual business impact isn't optional polish, it's what keeps the system trustworthy. The other trap is setting drift/anomaly thresholds too tight out of an abundance of caution: a threshold calibrated to catch every possible anomaly also catches normal seasonal variation (a retail report's numbers legitimately spike around a holiday), so thresholds need to account for known seasonality, not just raw statistical variance, or the alert noise problem just moves from 'too many failure alerts' to 'too many false anomaly alerts.'
One audience for a metric needs it within seconds or minutes; another needs it slower but guaranteed correct down to the penny (finance closing the books, for example). Design an architecture that serves both from the same underlying data: a fast, provisional view and a slower, authoritative one, including how you distinguish provisional from final in the UI and how the two get reconciled.
Sample Answer
Direct answer
When one audience needs a metric within seconds and another needs it slower but guaranteed correct, the answer is usually not to force one speed on both, but to serve two versions from the same underlying data: a fast, provisional view computed with less certainty, and a slower, authoritative view computed once all the data is truly in, with the two clearly labeled so nobody mistakes one for the other.
Structured elaboration
The provisional/fast path: computes an approximate or early answer from data as it arrives, accepting that late-arriving events (a delayed transaction, a retried request) might not be included yet, and that the number could still change before it's final. This path typically uses stream processing or frequent micro-batches to stay close to real time.
The authoritative/slow path: waits until a defined cutoff (end of day, a fixed processing delay) by which essentially all the relevant data is guaranteed to have arrived, then computes the final number once, which is treated as the number of record for anything that needs to be exactly right (financial reporting, anything audited).
Distinguishing provisional from final in the UI: the fast view needs an explicit, visible label ("provisional, as of [timestamp]") so a viewer doesn't mistake an early, possibly-incomplete number for the final one; without this, the two paths existing side by side actively creates confusion instead of solving the original problem.
Reconciliation: when the authoritative number is finally computed, it should be compared against what the provisional path was showing, both to validate the provisional path is generally trustworthy (are they usually close) and to give a concrete, auditable record of exactly how much a specific provisional figure ended up differing from the final one.
Processing guarantees: the fast path typically accepts an approximate correctness in exchange for speed (an at-least-once processing model that might occasionally double-count something transient, self-correcting on the next update), while the authoritative path needs exactly-once guarantees or an equivalent (idempotent, deduplicated processing) since it's the number that has to be exactly right.
Worked example
Finance needs an accurate revenue number to close the books at end of day; the product team wants to watch revenue trending in near real time throughout the day. The design: a streaming pipeline computes a provisional revenue figure updated every few minutes throughout the day, clearly labeled "Provisional, updated 2 minutes ago" on the product-facing dashboard, built to tolerate a small amount of double-counting from at-least-once delivery since it self-corrects on the next update. At end of day, a separate batch job runs once all transactions for the day are guaranteed to have settled (including any that were delayed or retried), computing the authoritative daily revenue with exactly-once processing guarantees; this becomes the number finance closes the books against and the number any historical dashboard shows for that date going forward. The reconciliation step compares the final provisional figure from just before midnight against the authoritative number computed hours later; a persistent gap larger than expected (say, provisional consistently running 3% low) would be a signal that the provisional path's assumptions need adjusting, not just a curiosity to note.
Trade-offs and pitfalls
The hardest part of this design in practice isn't the technical dual-path architecture, it's making sure the UI labeling actually prevents confusion; if "provisional" is a small gray label easy to miss, someone will eventually screenshot the provisional number into a deck as if it were final, and the entire point of the distinction is lost. The other real cost is maintaining two genuinely different processing pipelines (the fast streaming path and the slow authoritative batch path) rather than one, which is more infrastructure and more to monitor; before committing to this dual-path design, it's worth confirming the fast-path audience genuinely needs sub-minute freshness and would actually act differently with it, rather than just wanting the number sooner because faster feels better, echoing the same 'what decision changes' discipline used to choose report cadence generally.
Design a catalog and lineage system for a company's reporting layer: analysts need to discover what datasets and dashboards exist, who owns them, whether the data is fresh and trustworthy, and be able to trace a specific number on a dashboard back to the source tables and transformation jobs that produced it. What metadata do you capture, where does it come from, and how do you keep it from going stale?
Sample Answer
Direct answer
A catalog-and-lineage system for the reporting layer needs to answer three questions for anyone browsing it: what exists, can I trust it, and where did this specific number actually come from. That means capturing an inventory of datasets and dashboards with ownership and freshness status, plus a traceable path from any dashboard metric back to the source tables and transformation jobs that produced it.
Structured elaboration
What metadata to capture: for each dataset or dashboard, an owner, a description, a freshness/last-updated status, a data-quality signal (are its upstream tests passing), and usage statistics (is anyone actually querying it, which helps distinguish a maintained asset from an abandoned one). For lineage specifically, the transformation graph: which source tables feed which intermediate models, which models feed which dashboard, and at what step each metric's calculation actually happens.
Where it comes from: rather than a team hand-maintaining a wiki page (which goes stale immediately), metadata should be extracted automatically from the systems that already know it: the orchestrator or transformation tool (dbt-style tools already build a dependency graph as a side effect of running), the BI (business intelligence) tool's own API (which reports exist, who owns them, when they last refreshed), and the warehouse's query logs (which tables are actually being read, by what).
How you keep it from going stale: automation is the mechanism, not discipline. If lineage has to be manually updated every time someone adds a transformation step, it will drift out of date within weeks; the sustainable version instruments the pipeline itself (each transformation job registers its inputs and outputs as it runs) so the lineage graph is a byproduct of normal operation, not a separate maintenance task.
What this actually enables: an analyst finding a metric on a dashboard that looks wrong can trace backward through the lineage graph, dashboard to metric definition to intermediate model to source table, to find exactly which step introduced the problem, instead of guessing or re-deriving the whole pipeline by reading code.
Worked example
A dashboard shows weekly_active_users = 42,000 for a given week, and someone questions it. Tracing the lineage: the dashboard's tile is fed by a semantic-layer metric weekly_active_users, which resolves to a dbt model fct_user_activity, which is built from two upstream models, stg_login_events and stg_purchase_events, which are themselves built from raw login_events and purchase_events tables ingested by a specific ETL job (extract, transform, load) that last ran at 3:00 AM. If the number looks wrong, the catalog shows immediately whether stg_login_events is stale (its last-successful-run timestamp is old) or whether the raw ingestion job itself failed, narrowing the investigation from 'something in this whole pipeline is wrong' to 'this specific step, at this specific time, is the suspect' in seconds rather than requiring someone to read through every transformation by hand.
Trade-offs and pitfalls
Automated extraction only captures lineage for transformations the tooling can see; a hand-written SQL script run manually outside the orchestrated pipeline, or a calculated field built inside the BI tool itself rather than the semantic layer, creates an invisible gap in the graph, and those gaps are exactly where debugging gets hardest because the tool confidently shows a lineage chain that quietly stops one step short of the real source. Catalogs also tend to rot on the metadata side even when lineage is automated: an owner field that was accurate at creation time and never updated after a team reorg is worse than no owner field, because it actively misleads someone trying to find the right person to ask, which argues for periodically validating ownership against something authoritative (like an org directory) rather than trusting it forever once entered.
Explain the difference between a full refresh and an incremental (delta) refresh for a scheduled BI report or dashboard: when you'd choose each, what it implies for the upstream pipeline and warehouse storage, and how it changes what 'the SLA was missed' means operationally. Then, given a refresh_log(dataset_id INT, attempt_ts TIMESTAMP, status TEXT, duration_seconds INT) table and an SLA that every dataset must have had a successful run in the last 6 hours, write a query that returns every dataset currently breaching that SLA along with how many hours it's been since its last success.
Sample Answer
Direct answer
A full refresh rebuilds a report's entire dataset from scratch every time; an incremental (delta) refresh only reprocesses what's new or changed since the last successful run. Full refresh is simpler and self-correcting (it can't drift from a bug in a prior incremental run) but gets expensive at scale; incremental refresh is cheaper and faster but introduces real failure modes around what counts as 'new' and what happens when a refresh is missed or run twice.
Structured elaboration
-
When to choose each: full refresh fits smaller datasets or ones where correctness matters more than speed (you'd rather pay the cost of reprocessing everything than risk an incremental bug silently compounding over time); incremental refresh fits large datasets where reprocessing everything on every run is too slow or expensive, and where you can reliably identify what changed (a watermark column, a change-data-capture stream).
-
Implications for upstream ETL (extract, transform, load)/ELT (extract, load, transform) and warehouse storage: incremental refresh requires the upstream pipeline to reliably expose what changed since a given point (a
last_updatedtimestamp, an append-only change log), and the target table needs a strategy for applying that delta correctly (a merge/upsert rather than a blind append, so an updated row replaces its old version instead of creating a duplicate). Full refresh has no such requirement upstream but costs more compute and I/O on the warehouse side every single run. -
SLA (service-level agreement) and monitoring implications: an incremental refresh's SLA is only as trustworthy as the delta-detection logic; a subtle bug in what counts as 'changed' can cause a refresh to report success (job completed, no errors) while silently missing rows, which a naive monitoring setup (did the job succeed) won't catch, only a row-count or reconciliation check against the source will.
-
Failure modes unique to incremental refresh: (a) a missed run means the next incremental run needs to correctly pick up everything since the LAST successful run, not just since the last attempted run, or data silently gets dropped between the two; (b) a job that's re-run after a partial failure needs to be idempotent (re-running it shouldn't double-count rows that were already partially applied), which a naive append-only incremental design does not guarantee by default and has to be deliberately engineered for.
Worked example
Given refresh_log(dataset_id INT, attempt_ts TIMESTAMP, status TEXT, duration_seconds INT), and an SLA that every dataset must have had a successful run in the last 6 hours, here is a query that finds every dataset currently breaching that SLA and how long it's been since its last success:
WITH last_success AS (
SELECT dataset_id, MAX(attempt_ts) AS last_success_ts
FROM refresh_log
WHERE status = 'SUCCESS'
GROUP BY dataset_id),
all_datasets AS (
SELECT DISTINCT dataset_id FROM refresh_log)
SELECT
d.dataset_id,
CASE
WHEN ls.last_success_ts IS NULL THEN NULL
ELSE ROUND((JULIANDAY(:now) - JULIANDAY(ls.last_success_ts)) * 24.0, 2)
END AS hours_since_success
FROM all_datasets d
LEFT JOIN last_success ls ON ls.dataset_id = d.dataset_id
WHERE ls.last_success_ts IS NULL
OR (JULIANDAY(:now) - JULIANDAY(ls.last_success_ts)) * 24.0 > 6.0
ORDER BY d.dataset_id;
(:now is the pipeline's current-time parameter; SQLite's JULIANDAY is used here for the date-math example, the equivalent in other engines is TIMESTAMPDIFF/DATEDIFF or similar.)
Executed against a 5-dataset sample with :now pinned to 2026-01-10 18:00:00: dataset 1's last success was 5.5 hours earlier (within SLA, correctly excluded); dataset 2's last success was 10 hours earlier (breaching, returned with hours_since_success = 10.0); dataset 3's last success was 10 minutes earlier (within SLA, excluded); dataset 4 has never had a successful run at all (breaching, correctly returned with hours_since_success = NULL rather than a misleading fabricated number, since there's no prior success to measure from); dataset 5's last success was exactly 6.0 hours earlier, landing right on the boundary and correctly excluded since the condition is a strict > 6.0, matching the SLA's 'within the last 6 hours' wording. The full output: (2, 10.0) and (4, NULL).
Trade-offs and pitfalls
The NULL case for a dataset that has never succeeded is a real design decision, not an edge case to gloss over: returning a fabricated large number (treating 'never succeeded' as if it happened at the start of time) would technically flag it as breaching but obscure that this is a fundamentally different, likely more urgent, situation than a dataset that's simply running late. The boundary case (exactly 6.0 hours) also matters in practice: whether the SLA is a strict > or an inclusive >= should be an explicit decision matching how the SLA is actually worded and communicated to stakeholders, not an accident of whichever comparison operator got typed first, since it determines whether a dataset right at the edge gets flagged or not.
A business runs on dozens of Excel-based reports that everyone knows are fragile, and leadership wants to move them onto a real BI platform. Plan the migration: how you'd discover and prioritize what exists, what changes when you rebuild the underlying data model instead of an Excel formula chain, and how you'd validate that the new dashboards actually match the old numbers before cutting over.
Sample Answer
Direct answer
Migrating dozens of fragile Excel-based reports to a real BI (business intelligence) platform, or migrating hundreds of dashboards from one BI platform to another, is fundamentally a discovery-and-validation problem more than a technical one: you have to find out what actually exists and how much people depend on it before you can safely prioritize, rebuild, and cut over, and the validation step, proving the new version matches the old, is what actually earns trust for the cutover.
Structured elaboration
Discovery: for Excel-based reports, this means stakeholder interviews to find out what reports actually exist (they're rarely centrally cataloged), who depends on each one, and how it's actually used, since a spreadsheet's formulas alone don't tell you the business logic embedded in it. For platform-to-platform migration, discovery is more structured (an inventory of existing dashboards, their usage stats, and their definitions) but the same underlying question applies: what's actually load-bearing versus abandoned.
Prioritization: not everything needs to migrate at the same urgency; prioritize by actual usage and business impact, migrating the highest-value, most-depended-on reports first (both because they matter most and because getting the process right on a high-visibility report builds confidence for the rest) or, alternatively, starting with lower-stakes reports to work out process kinks before touching anything critical, a real judgment call depending on organizational risk tolerance.
Rebuilding the data model: an Excel report's "logic" is often scattered across cell formulas, pivot tables, and manual copy-paste steps; the migration isn't just moving that logic verbatim into a new tool, it's an opportunity (and a necessity) to rebuild it as a proper, maintainable data model, which is real work, not just a file conversion. Platform-to-platform migration has a related but different challenge: automated conversion tools can handle some of the translation, but complex calculated fields or platform-specific features often need manual rebuilding and can't be fully automated.
Validation before cutover: for every migrated report, compare its output against the original for a fixed historical period and confirm they match (or understand and can explain every difference), which is the concrete evidence that lets stakeholders trust the new version enough to actually switch.
Rollout and decommissioning: a parallel-run period where both old and new versions are available lets people build confidence before the old version goes away; decommissioning the old version too early, before trust is established, risks a painful rollback if a discrepancy surfaces after the fact.
Worked example
Migrating 50+ Excel-based sales reports to an interactive BI platform: discovery interviews with the sales operations team surface that of roughly 60 spreadsheets in circulation, only about 35 are actually still in active use, the rest are abandoned or superseded, immediately cutting the real migration scope. The 35 are prioritized by how many people depend on them and how business-critical the decisions they inform are; the top five (feeding weekly leadership reviews) migrate first. For each, the underlying Excel formulas are traced and rebuilt as a proper data model in the new platform rather than copied verbatim, since several formulas turn out to reference other cells with undocumented business logic (a manually-applied adjustment for a specific known data issue) that needs to become an explicit, documented rule in the new model instead of a hidden spreadsheet quirk. Before cutover, each new dashboard's output is validated against the corresponding spreadsheet's last three months of numbers; one discrepancy is found and traced to the spreadsheet silently excluding a specific product category that the person who built it years ago had manually filtered out for reasons nobody currently remembers, which becomes an explicit, documented decision in the new system rather than an invisible one. A month-long parallel-run period lets the sales team cross-check the new dashboards against the spreadsheets they still trust before the spreadsheets are formally retired.
Trade-offs and pitfalls
The single most underestimated part of this kind of migration is how much undocumented business logic lives inside "just a spreadsheet": formulas that encode a specific person's judgment calls from years ago, made without documentation, that nobody can fully explain anymore, and unless the migration process specifically hunts for and surfaces these (through the validation-against-history step, not just a technical formula-to-formula translation), the new system either silently drops real business logic or silently reproduces an undocumented quirk nobody actually meant to keep, and the difference between those two outcomes is only visible if someone deliberately investigates every discrepancy rather than assuming a close-enough match is good enough.
Unlock Full Question Bank
Get access to hundreds of Business Intelligence, Reporting, and Dashboards interview questions and detailed answers.
Sign in to ContinueJoin thousands of developers preparing for their dream job.