Data Pipeline Architecture and Design Questions
End-to-end design of data pipelines: source-to-sink flow, staging layers, idempotency, backfills, and reprocessing. Covers choosing between batch and streaming stages, decoupling ingestion from transformation, and designing for evolvability. The foundational systems-design skill for a data engineering interview.
A KPI turns out to be wrong. Walk through how you'd use lineage information to trace back through the pipeline and find which upstream table or transformation caused it.
Sample Answer
Direct answer
Start at the KPI's own defining table or view and walk its lineage graph upstream one hop at a time, using whatever lineage source is available (a transformation tool's dependency graph, a data catalog, or the warehouse's own query-history metadata) to list its immediate producers. Then prioritize which of those to inspect first by what changed most recently and which carries the most complex logic, rather than checking every upstream table with equal weight, and confirm a hypothesis by comparing actual numbers against historical baselines before calling it the root cause.
Structured elaboration
- Confirm the symptom precisely. Which number is wrong, since when, and by how much. The "since when" matters most, because it turns an open-ended search into "what changed upstream around that date."
- Pull the first-pass dependency graph from tooling, not from memory. A lineage tool, whether it's a transformation framework's dependency graph, a data catalog, or the warehouse's own lineage or query-history view, gives the KPI's immediate upstream tables and transformations in seconds. This should always be the first move, before reading any transformation logic by hand.
- Prioritize the candidates instead of sweeping all of them:
- Recency of change is the strongest signal; a code or schema change close to when the KPI diverged is the top suspect.
- Logic complexity matters next; joins, window functions, and aggregations hide subtle bugs far more often than a straight pass-through does.
- Recent operational incidents on a source (a known late or failed load) are an obvious, cheap first check.
- Validate quantitatively, not by inspection alone. Compare a suspect's current row counts, key distributions, or aggregate values against its own historical baseline for the same period. A real KPI bug shows up as a measurable divergence somewhere in the chain, and that comparison either confirms or rules out a candidate before more time is spent on it.
- Fix at the actual point of defect, not by patching the KPI layer to compensate; patching the transformation or coordinating with the upstream data owner, then re-running affected models forward, is what actually resolves it rather than hiding it.
- Add a targeted check to prevent recurrence on exactly the field or transformation that broke, so the same failure class is caught before it reaches the KPI again.
Worked example
Say the monthly revenue KPI comes in 8 percent below expectation for November, and the trace starts at the orders table feeding the revenue model. The typical daily order count in November is about 150,000. On November 14, the day the divergence first appears, the orders table shows only 122,000 rows:
150,000150,000−122,000=18.7% single-day dropSpread across a 30-day month, one day's 18.7 percent shortfall contributes roughly:
3018.7%≈0.62% to the monthly totalThat's far smaller than the 8 percent monthly miss actually observed, which rules out the single-day volume dip as the primary cause and points the trace toward a sustained, multi-day issue instead. Following the lineage graph one more hop, to the pricing table the revenue model joins against, turns up a schema change (a new discount field) that landed around the same time and caused the join to double-count discounted rows for every day the field has existed, a defect whose scale (spread across many days, not one) is consistent with an 8 percent sustained miss. The arithmetic above is what rules the first hypothesis out and justifies moving one hop further upstream, rather than stopping at the first plausible-looking suspect.
flowchart LR
A[KPI shows unexpected value] --> B[Pull lineage graph from KPI object]
B --> C[List immediate upstream sources]
C --> D[Prioritize by recency and logic complexity]
D --> E[Compare suspect vs historical baseline]
E -->|rules out| D
E -->|confirms| F[Fix at the actual source]
F --> G[Add targeted check to prevent recurrence]
Trade-offs & pitfalls
- Reading every model's transformation logic by hand before checking the automated lineage graph wastes time the tooling already answers in seconds; lineage-first is almost always the faster path.
- Checking every upstream table with equal priority, instead of ranking by recency and complexity, turns a targeted trace into an unfocused audit that takes far longer than it needs to.
- Patching the KPI view itself to compensate for a known-bad upstream input, instead of fixing the actual defective transformation, hides the bug until the next time that upstream table feeds something else.
- A common wrong turn is treating lineage as purely structural (what depends on what) without also checking when each dependency last changed; the timing correlation is usually what actually narrows the search from many candidates to one.
You discover an upstream bug corrupted a key metric for the last 90 days. Design a backfill to correct the historical data without breaking the dashboards and reports that are actively being used right now.
Sample Answer
Direct answer
Recompute the corrected 90 days into a separate staging area using the fixed transform logic, verify the staged output against the current live data with automated checks, then swap the corrected partitions into place atomically, one bounded time window at a time, so dashboards keep reading a consistent, even if briefly stale, version throughout and never see a half-recomputed state.
Structured elaboration
Why staging plus atomic swap, not in-place correction
Correcting rows in place while dashboards are live means any query that runs mid-correction sees a mix of old and new values for the same metric, which is worse than being uniformly wrong. Writing the corrected 90 days to new, parallel storage and only swapping the read pointer once a whole window is verified keeps every read consistent: either the old, known-wrong-but-internally-consistent version, or the new, corrected-and-verified version, never a blend of both.
Windowing the backfill
Recompute in bounded time chunks rather than all 90 days at once. Each chunk gets verified and swapped independently, so a defect found partway through does not block chunks already verified, and a bug in the fix itself is caught on one small chunk before it has been applied to all 90 days.
Determinism is a precondition, not a nice-to-have
Before touching real data, confirm the corrected transform is deterministic: recomputing the same input window twice, same code, same raw input, must produce identical output. This matters just as much when the corrected logic is a code-driven feature transform feeding a model as when it is a simple metric aggregation: either way, running the backfill twice or out of order must not silently recompute the same window into two different results. If the fix relies on anything time-dependent or order-sensitive, such as a default that reads the current time, or an aggregation whose result depends on processing order, running the backfill twice or out of order produces different corrected values, which defeats the audit trail this whole design is trying to establish. Confirm reproducibility on one chunk before running the rest.
Verification before swap
Compare staged output to current live output for the same window on row counts, key aggregate totals (with the specific corrected metric expected to differ and every other metric expected to match exactly), and referential completeness, checking that the fix introduced no orphaned keys. Only swap a chunk once its verification passes; a failed chunk stays on the old, wrong data until investigated, without blocking chunks that already passed.
Communication as part of the design
Since dashboards stay live throughout, tell stakeholders which historical dates are still on the old, known-wrong numbers and which have already swapped to corrected values, so nobody draws a conclusion from a number mid-flight.
flowchart LR
Raw[Raw immutable source] --> Fix[Corrected transform logic]
Fix --> Staging[(Staging: recomputed partitions)]
Staging --> Verify{Verification checks pass?}
Verify -- Yes --> Swap[Atomic partition swap]
Verify -- No --> Retry[Investigate and re-run]
Swap --> Live[(Live tables: dashboards keep reading)]
Worked example
Assume the corrupted metric is computed from roughly 50 million source rows per day:
50,000,000 rows/day×90 days=4.5 billion rows to recompute
Chunking the backfill into 7-day windows:
90 days÷7 days/batch=12.86⇒13 batches (last one partial, 6 days)
which puts roughly
13 batches4.5×109 rows≈346 million rows per batch
a size the verification step needs to run against once per batch, thirteen times total, rather than once against the full 4.5 billion rows, which is what makes an early bug in the fix cheap to catch instead of expensive.
Trade-offs & pitfalls
- Correcting live tables in place, even for "just one column," is not atomic from a concurrent reader's point of view unless the store guarantees it, and most analytical stores do not at the row level during a bulk update.
- Running all 90 days as one giant job means a bug discovered on day 60 requires redoing all 90, instead of redoing one already-isolated chunk.
- Smaller chunks catch problems earlier and reduce blast radius but add coordination overhead (more swap events, more verification runs); chunk size should track how much the fix is trusted, not a fixed default.
- Skipping the determinism check because the fix "looks simple" is a common wrong turn; a fix that is not reproducible turns the audit trail, what changed, why, and by how much, into something nobody can reconstruct later.
What should go into a data contract between a team that produces a dataset and the teams that consume it, and how would you actually enforce it rather than just document it?
Sample Answer
Direct answer
A data contract needs three layers: what the data looks like (schema, types, semantics), what service level the producer promises (freshness, availability, and a compatibility policy for future changes), and who is accountable when either breaks (ownership and escalation path). Enforcement means those layers are checked automatically at the moment a break would actually happen, before a producer ships a change and continuously against what is actually being served, rather than living only in a wiki page that nobody re-reads under deadline pressure.
Structured elaboration
| Contract element | Contents |
|---|---|
| Schema and semantics | field names, types, nullability, units, allowed value ranges, enum meaning, canonical serialization format |
| Service levels | freshness (maximum data age), availability, latency and throughput bounds |
| Compatibility policy | what counts as an additive versus a breaking change, and the required deprecation notice window for breaking ones |
| Ownership | producer and consumer contacts, on-call path, escalation route |
Enforcement mechanisms, focused on when each one fires:
- Schema registry gate: the producer's build must pass a compatibility check against the registered schema before a change can ship, catching a breaking change before production, not after.
- Runtime validation: ingestion rejects or quarantines records that violate the contract instead of letting them flow through as silent nulls or coerced types.
- Consumer-driven checks: tests that fail the producer's build if a real consumer's stated expectations would break, so a producer is not only checking its own schema in isolation.
- Staged rollout of enforcement itself: new contracts start in alert-only mode and tighten to blocking mode over time, so enforcement does not halt unrelated work the first day it exists.
Worked example
A catalog team produces a shared product dataset (product_id, name, price, category, currency), consumed by a pricing service, a search index, and a recommendation pipeline. The contract specifies that price is always an integer in minor units (cents), category is one of a fixed enum, and only additive changes ship without notice, a renamed or removed field requires two weeks' notice. Enforcement: the catalog team's build runs a schema-compatibility check against the registry before any migration ships. If a change tries to remove category or switch price from integer cents to a float dollar amount, that check fails the build immediately, because both are breaking changes the contract explicitly covers, catching the problem before the recommendation pipeline (which assumes integer cents) would have silently mispriced results for every consumer downstream.
Trade-offs & pitfalls
Writing the contract as documentation only, without the registry and build-time checks, means it decays the first time someone ships in a hurry; a contract nobody's tooling reads is a contract nobody actually follows under pressure. Enforcing everything in blocking mode from day one, on an organization with no existing baseline compliance, halts unrelated work and burns trust in the contract itself; starting in alert-only or pilot mode and tightening over time avoids that. A contract that only covers schema shape and ignores semantics, a currency or unit change that does not change the field's type, can pass every schema check and still silently break every consumer that assumed the old meaning.
A pipeline produces hourly revenue aggregates, but a meaningful fraction of events arrive after the hour has already been closed and reported. How do you decide when an hour is 'final,' and what do you do when data shows up after that point?
Sample Answer
Direct answer
Deciding an hour is "final" is a deliberate trade-off between completeness and timeliness, not something you can observe directly: pick a delay, a watermark, after which you stop waiting for more events and publish, accepting that some fraction of late data will still arrive after that point. What happens to data that shows up after that point should be either folded into a scheduled correction pass or routed to a separate reconciliation channel, never used to silently overwrite an already-reported number without telling downstream consumers it changed.
Structured elaboration
Why lateness happens, and why it shapes the watermark choice
Network retries occur when a client or upstream service resends an event after a timeout, so it arrives well after its original event time; this tail is usually short. Client-side batching happens when a source buffers events locally and flushes in bulk, such as a mobile client syncing once connectivity returns, producing a burst of "old" events arriving together, sometimes hours later, a much longer tail. Clock skew happens when a source's clock and the pipeline's clock disagree, so an event's recorded timestamp does not match when the pipeline actually receives it; this is usually small and bounded. The watermark should be informed by which of these causes dominates for the specific source, not by a single default applied to every pipeline.
Choosing the watermark
The watermark is the point past which the pipeline stops waiting and calls the hour done. Setting it later captures more of the tail (more complete) but delays every "final" number by that much (less timely); setting it earlier publishes faster but reports a number known to be incomplete. This should be a stated decision the business signs off on as a service-level objective (SLO), for example that hourly revenue is final four hours after hour-close, not an implementation detail left to whoever wrote the job.
What happens after the watermark passes
Two structurally different responses, and the choice matters. A scheduled correction keeps the door open on a fixed cadence, recomputing the last several hours nightly, so late-arriving data does get folded in, just not immediately; this works when consumers can tolerate a number changing at a known, predictable time. A reconciliation channel instead routes very-late events to a separate stream, folding them back only through an explicit backfill when someone actually needs the fully corrected number for that period; this avoids surprising every dashboard consumer with silent changes, at the cost of the aggregate staying slightly wrong until someone asks. Either way, tag every published aggregate with its own watermark or as-of timestamp, so a consumer can always tell whether a number could still move.
Communicating "final" honestly
"Final" should mean the pipeline has committed to not automatically recomputing this period, not that no more data could possibly exist for it. Publish both the number and its watermark so stakeholders can distinguish a provisional read from a committed one, and alert on corrections that are far larger than the normal late-arrival tail, since those usually signal an upstream problem rather than routine lateness.
Worked example
Say network retries typically resolve within two minutes and clock skew is bounded to under one minute, but a client-batching source can flush events up to six hours late. Setting the watermark at five minutes clears the two fast causes but ignores the batching tail entirely; a six-hour-plus watermark would capture the tail but delay every hourly number by a quarter of a day. The workable answer is a short watermark, five to ten minutes, for the number that is "final at hour-close," plus a scheduled nightly correction pass that specifically re-includes the batched-client tail, rather than picking one watermark trying to cover both causes at once.
Trade-offs & pitfalls
- Picking one watermark sized for the worst-case lateness cause, such as six-plus hours for client batching, and applying it to every metric delays every number to accommodate a rare tail.
- Silently overwriting a previously reported "final" number when late data arrives, with no record that a correction happened, costs stakeholder trust when numbers move with no explanation.
- A scheduled correction pass is simpler to reason about but makes delayed corrections routine by design; a reconciliation channel avoids surprising anyone but can leave the "official" number wrong indefinitely if nobody triggers the backfill.
- Treating "final" as a technical property of the data, all events received, rather than a business decision about how much lateness is worth waiting for, is the most common wrong turn.
Once a pipeline has multiple dependent stages, what does a workflow orchestrator actually give you that a plain cron job doesn't?
Sample Answer
Direct answer
A cron job only knows a clock time; it has no idea whether the upstream stage it depends on actually finished, failed, or is still half-written, so a multi-stage pipeline built on cron entries is really a set of independent guesses about how long each stage takes. A workflow orchestrator (Airflow, Dagster, Prefect and similar) models the pipeline as a directed acyclic graph (DAG) of tasks with explicit dependency edges, so a downstream task only starts when its upstream task actually reports success, and the system gives you retries, backfills, and one place to see the whole pipeline's health for free.
Structured elaboration
Dependency-awareness versus time-awareness. Cron triggers on wall-clock time alone. If stage two is scheduled 30 minutes after stage one, that gap is a guess about stage one's runtime, not a guarantee. An orchestrator's DAG edge says "task B runs after task A succeeds," which is true regardless of how long A actually takes that day.
Failure propagation. In a DAG, if a task fails, its downstream dependents simply do not run (or run a defined failure branch), automatically. With cron, each stage fires on its own schedule no matter what happened upstream, so a failed stage one silently lets stage two start against stale or partial data.
Retries with policy. Orchestrators let you attach a retry policy per task (bounded attempts, backoff), so a transient failure recovers without a human noticing. A cron job that fails just waits for its next scheduled fire, however far away that is.
Backfill and replay. Because tasks are parameterized by the DAG's logical run (a date or partition), you can rerun a single historical slice through the whole dependency chain. Cron has no concept of "this stage, for that date," so backfilling means manually re-invoking scripts in the right order.
Observability. A DAG-based orchestrator gives one UI or API surface for task duration, success/failure history, and where in the chain something is stuck. Cron gives you whatever each script happens to log, scattered across machines and cron logs.
When cron is still the right tool. A single, independent job with no downstream dependents and no need for backfill or retry policy does not need a DAG orchestrator; adding one there is pure overhead.
Worked example
Take a 3-stage pipeline: extract, transform, load. With cron, each stage gets a fixed offset:
extract=01:00,transform=01:30,load=02:00This assumes extract always finishes within 30 minutes. If the source is slow once and extract takes 45 minutes, transform still fires at 01:30 and reads a half-written file; cron has no signal that would stop it. Generalizing to a chain of N stages, cron needs N independently tuned fixed offsets, each one a manual guess that has to be re-tuned whenever runtime varies:
cron offsets needed=N,DAG dependency edges needed=N−1For N=5: cron requires 5 separately maintained time guesses; the DAG requires 4 edges, and those edges stay correct no matter how long any one stage takes, because the trigger is "the previous task instance succeeded," not a clock offset.
Retries make the gap sharper. Suppose a task fails transiently (a brief network blip to the source). An orchestrator retries with exponential backoff, say attempts at 1, 2, and 4 minutes after failure:
1+2+4=7 minutes until the task is resolved or escalatedA cron-based stage that fails has no retry concept built in: the next opportunity to reprocess is the next scheduled fire, often the next day:
24×60=1440 minutes before the same job runs againThat gap, 7 minutes versus 1440 minutes, is the practical cost of "no retry semantics," not an abstract one.
Trade-offs & pitfalls
An orchestrator is another system to run and keep healthy (unless you use a managed offering), so standing one up for a single independent job with no dependents is pure overhead with no payoff. Sensor or polling patterns (a task that waits by repeatedly checking for a file or a signal) can add their own latency or cost if the poll interval is misconfigured, so "dependency-aware" isn't automatically "instant." A common wrong turn is putting heavy transformation logic directly inside orchestrator tasks instead of having the orchestrator call out to dedicated compute; the orchestrator's job is to sequence and observe work, not to do the work itself.
Unlock Full Question Bank
Get access to all 26 Data Pipeline Architecture and Design interview questions and detailed answers.
Sign in to ContinueJoin thousands of developers preparing for their dream job.