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 multi-step pipeline stage depends on a flaky third-party API. Design its retry and failure-handling behavior: how many retries, what backoff, and what happens to a record that still fails after all of them.
Sample Answer
Direct answer
Bound the retries with a capped exponential backoff and jitter (for example, 5 attempts, delay doubling each time up to a ceiling), retry only failures classified as transient, and once retries are exhausted route the record to a dead-letter queue (DLQ, a separate holding queue for records a pipeline could not process) with full failure context rather than dropping it or blocking the whole run.
Structured elaboration
Classify before retrying. Not every failure deserves a retry. Split errors into transient (timeouts, connection resets, rate-limit responses, 5xx-class server errors) and permanent (malformed request, authentication failure, a business-rule rejection from the API itself). Only the transient class should ever be retried; retrying a permanent failure wastes time and delays the record's arrival in the DLQ for no benefit.
Shape the backoff deliberately.
- Exponential growth (each retry waits roughly twice as long as the last) spreads retries out instead of hammering an already-struggling dependency at a fixed interval.
- Jitter (randomizing the delay slightly) prevents every failed record from retrying in lockstep and re-creating the same burst of load that caused the failure.
- A ceiling on the maximum delay keeps a single record's retry sequence from silently blowing past whatever latency the pipeline's service-level agreement (SLA, the commitment on how fresh downstream data must be) allows.
- A hard cap on attempt count guarantees every record eventually resolves one way or the other, rather than retrying forever.
Make retries safe, not just present. A retry is only safe if repeating the call cannot cause harm beyond the first successful application, meaning the call needs to be idempotent (applying it more than once has the same effect as applying it once), either because the operation is naturally read-only or because a stable idempotency key lets the receiving side recognize and ignore a duplicate write.
Protect the dependency, not just the record. If a meaningful fraction of calls to the same API start failing within a short window, that's a signal the dependency itself is down, not that this one record is unlucky. A circuit breaker (a mechanism that stops sending calls to a dependency once its failure rate crosses a threshold, and periodically tests whether it has recovered) avoids retrying every in-flight record individually into a dependency that isn't coming back soon.
Decide where the retry actually lives. Retrying should happen at the queue or consumer boundary (re-delivering the message after a delay) rather than as a tight loop inside the worker that's handling the record, so the worker stays free to process other records while one waits out its backoff.
What happens after the last retry. The record is never silently dropped. It goes to the DLQ carrying the original payload, the specific error, a retry count, and timestamps of the first and last attempts, so a human or an automated remediation step has everything needed to fix and safely replay it later.
Worked example
Take a policy of base delay 1 second, backoff factor 2, a 60 second ceiling, and a maximum of 5 attempts. The delay before attempt n is:
delay(n)=min(1s×2n−1, 60s) delay(1)=1s, delay(2)=2s, delay(3)=4s, delay(4)=8s, delay(5)=16sNone of these hit the 60 second ceiling yet, so the worst-case wall time from first failure to landing in the DLQ (ignoring jitter and the API's own response time) is the sum of the delays before each retry:
1+2+4+8+16=31sWith jitter of plus-or-minus 20 percent applied to each delay, the same worst case falls somewhere between:
31s×0.8=24.8sand31s×1.2=37.2sSo a record that is genuinely unrecoverable still takes roughly 25 to 37 seconds to reach the DLQ under this policy, a number the team can check against the pipeline's actual freshness requirement to decide if 5 attempts and a 60 second ceiling are the right choice, or too slow, or too aggressive.
flowchart LR
A[Pipeline stage] --> B[Call third-party API]
B -->|success| C[Continue downstream]
B -->|transient error| D[Backoff retry queue]
D -->|attempts remain| B
D -->|attempts exhausted| E[Dead-letter queue]
B -->|permanent error| E
E --> F[Remediation]
F --> G[Replay, new attempt]
G --> B
Trade-offs & pitfalls
- Aggressive retry counts or a flat, non-exponential interval can amplify load on a dependency that is already struggling; jitter and a circuit breaker exist specifically to avoid turning a retry policy into a self-inflicted denial-of-service.
- Retrying failures indiscriminately, without classifying transient versus permanent, burns the entire retry budget on errors that will never succeed and only delays the record's arrival at the DLQ.
- Idempotency is the load-bearing assumption of the whole design: skip it, and retries turn into duplicate side effects (a record processed twice, a write applied twice) instead of a safety mechanism.
- A DLQ with no remediation and replay path is just a slower, more expensive way to drop data; the retry design isn't complete until there's a way to get a corrected record back into the flow.
- A common wrong turn is implementing the retry as a blocking loop inside the worker handling the record, which stalls that worker (and everything queued behind it) for the full backoff duration instead of freeing it to process other records.
What would you actually put on an observability dashboard for a data pipeline, and why do each of those signals matter?
Sample Answer
Direct answer
At minimum, a pipeline dashboard should show freshness (how old the newest data is), completeness or volume (whether the expected amount of data actually arrived), latency (how long the pipeline took, per stage and end to end), error or failure rate, and a couple of data-quality signals (like null rates or schema drift on key fields). Together these answer "is the data here, is it on time, and is it trustworthy" without anyone needing to read logs first; a job reporting "success" alone can't answer any of those three questions.
Structured elaboration
| Signal | What it measures | Why it matters |
|---|---|---|
| Freshness | Time since the newest record or partition landed, compared to when it was expected | Catches stale data reaching consumers before anyone downstream notices or complains |
| Completeness / volume | Row or file counts against a historical baseline | Catches partial or truncated loads that a "job succeeded" status alone would miss entirely |
| Latency | How long each stage, and the pipeline end to end, actually took | Surfaces slow degradation early, before it becomes an outright breach |
| Error / failure rate | Proportion of tasks or records failing | A direct pipeline-health signal, distinct from whether the data itself is correct |
| Data-quality signals | Null rates and schema drift on the columns downstream logic actually depends on | A pipeline can finish successfully and still hand consumers wrong data; this is the only category of the five that would catch that |
| Backlog / consumer lag | How far behind real time the pipeline is currently running | An early warning that shows up before freshness or latency actually breaches a threshold |
What decides what belongs here versus what belongs to a full alerting design. The dashboard's job is to let someone without deep pipeline knowledge tell, at a glance, whether the data currently on it is trustworthy. Deciding exactly which breach pages someone and which is quietly logged is a separate design question about thresholds and escalation; for an entry-level "what goes on the dashboard" answer, naming the six signals above and why each matters is the proportionate depth.
Worked example
Take a daily sales pipeline expected to finish by 02:00 UTC with a typical row count around 500,000.
- Freshness: data landed at 01:40, 20 minutes ahead of the deadline. Fine.
- Completeness: 494,200 rows landed against the 500,000 baseline, a shortfall of
which is within a normal day-to-day variance band, so it reads fine on the dashboard.
- Latency: the run took 55 minutes against a typical 40 to 50 minute range, mildly elevated, worth a glance but not alarming on its own.
- Error rate: 3 of 40 tasks needed a retry:
against a normal baseline around 2 percent, which is the one figure on this dashboard that stands out enough to warrant a closer look.
- Null rate on the revenue column: 0.02 percent, well within tolerance.
This is what "why each signal matters" looks like in practice: four of the five numbers are unremarkable, and the dashboard's job was to make the one that isn't (the error rate) visible at a glance, without anyone needing to dig through task logs first.
Trade-offs & pitfalls
- Overloading the dashboard with every metric the system can emit defeats its purpose; someone should be able to judge overall health in under a minute, not scroll through fifty panels.
- Treating job-succeeded status as sufficient is the main pitfall: a job can report success against empty or truncated input, and only the completeness and data-quality signals would catch that.
- A static, hardcoded threshold ages badly against seasonal patterns (weekday versus weekend volume, for example); comparing against a rolling historical baseline is more proportionate than picking one fixed number, without needing to design the full paging and escalation policy that decision requires elsewhere.
- Leading with infrastructure metrics (CPU or memory usage) instead of these data-centric signals answers a different question than the one most stakeholders actually have, which is whether their data is present and correct.
What is a backfill in a data pipeline, and what kinds of situations actually force you to run one?
Sample Answer
Direct answer
A backfill is running a pipeline's transformation logic against a range of already-elapsed time (or already-landed data) to produce the output that should exist for that range, because it never ran, ran incorrectly, or needs to reflect a rule that did not exist when the data was first processed. It targets the past on demand, unlike the pipeline's normal incremental run, which only ever moves forward onto newly arriving data.
Structured elaboration
A backfill is forced by one of a small number of situations, and naming which one you're in changes how you'd run it:
- Outage / gap: the pipeline simply did not run for some window (a scheduler failure, an upstream source down), so there is no output for that period at all.
- Bug in the transform: the pipeline ran, but the logic was wrong, so a past window's output needs replacing, not filling in.
- Definition change: a metric or feature's meaning changes (a new business rule, a corrected calculation), and history needs to reflect the new definition for comparability.
- Late-arriving source data: upstream systems deliver records after the window that consumed them already closed and published.
- New pipeline / dataset onboarding: a pipeline is switched on today but needs history from before it existed.
- Upstream schema or identifier change: already-landed raw data needs reprocessing once a mapping or fix is available.
- Disaster recovery: a downstream table was lost or corrupted and needs to be rebuilt from immutable raw sources.
The first three are the ones that actually change what the output should say (correction, redefinition); the rest are about filling in output that should already exist. That distinction matters because a correction or redefinition backfill has to reconcile with whatever already consumed the old, wrong output, while a gap-fill or historical load usually does not.
Worked example
A daily active users pipeline retains 400 days of partitioned output. A dedupe bug is found that undercounted the metric for the last 40 days only; the other 360 days were computed before the bug was introduced. The backfill only needs to touch the 40 affected date partitions, not the full 400:
40040=0.10
so the reprocessing cost is proportional to 10% of the retained table, not 100%, because the affected range is scoped by partition. Compare that to onboarding this same pipeline fresh today with no prior output: that backfill has to cover all 400 days, a full historical load, because there is no "already correct" portion to leave alone. The cost driver in both cases is the same: how many partitions actually need to change, not the size of the whole table.
Trade-offs & pitfalls
Scoping a backfill to only the affected partitions (using deterministic, idempotent writes keyed by partition) is what keeps the cost proportional to the actual damage; reprocessing the whole table "to be safe" trades a real, avoidable compute cost for a false sense of thoroughness. Blindly appending backfilled rows instead of overwriting the affected partitions atomically produces duplicates. The most common wrong turn is forgetting that anything materialized on top of the reprocessed table (rollups, dashboards, downstream models) also has stale output for that same window and needs its own rerun once the backfill lands, a backfill that stops at the first table it touches leaves the corruption one layer further downstream.
Design the monitoring and alerting for this pipeline: what would trigger a page versus a quiet log entry, and how would you avoid drowning the on-call engineer in noise?
Sample Answer
Direct answer
Reserve paging for issues that are both confirmed (sustained over more than one check, not a single blip) and actionable right now by the person being paged. Route everything else, especially first-occurrence anomalies and slow-moving trends, to a quiet log or dashboard reviewed on a cadence. Noise is controlled by comparing against rolling historical baselines instead of fixed numbers, requiring a breach to persist across several evaluation windows before it pages, and automating the remediations that are already well understood so a page is reserved for what actually needs a human decision.
Structured elaboration
The core decision:
| Question | Page (critical) | Log or dashboard only |
|---|---|---|
| Is the pipeline currently failing to deliver correct data (repeated hard failures, latency threatening the pipeline's service-level agreement (SLA), a dead-letter queue growing sharply)? | Yes | |
| Is it a single transient blip, a deviation the pipeline is expected to self-correct, or a slow drift that matters over days but isn't urgent right now? | Yes |
Noise-reduction techniques, and why each one earns its place:
- Baseline against rolling history rather than a fixed number. A static "must process more than X rows" threshold breaks the first time volume has a legitimate seasonal dip; a trailing baseline adapts with it.
- Require a sustained breach across several consecutive evaluation windows, not a single sample. One noisy reading crossing a line is common; requiring it to hold avoids paging on something that resolves itself before anyone even looks.
- Tier the thresholds (a lower bar for a non-urgent notification, a higher one for an actual page), so the person on call can tell "worth knowing" apart from "worth waking up for."
- Deduplicate and group related alerts. One upstream outage triggering separate alerts on every downstream consumer should collapse into a single page pointing at the shared root cause, not one page per symptom.
- Automate the remediations that are already well understood (retrying a transient failure, scaling up against a growing backlog), so a page is reserved for the cases a script genuinely can't decide on its own.
Escalation structure. A tiered path works best: a warning goes to a team channel, a sustained critical breach pages the on-call engineer directly, an unacknowledged critical page escalates to a secondary responder after a fixed window, and one still unresolved after a further window escalates again, to an engineering lead. This exists so an alert actively being worked doesn't independently keep escalating, while one that's genuinely stuck doesn't sit unowned indefinitely.
Worked example
Say the pipeline's hourly throughput baseline (a trailing 28-day average for that same hour of day) is 500 rows per second with a standard deviation of 50. Set the warning line at 2 standard deviations below baseline and the critical line at 3:
warning=500−2(50)=400 rows/sec critical=500−3(50)=350 rows/secRequire the warning to hold for 10 minutes and the critical for 5 before either fires. If observed throughput drops to 380 rows per second and holds there for 12 minutes, it has crossed the warning line (400) and stayed below it long enough to notify the team channel, but it hasn't yet reached the critical line (350), so it does not page. If it deteriorates further and holds below 350 for 5 minutes, it pages. The tiering keeps a real, sustained degradation from being ignored while not paging anyone at the very first dip below baseline.
flowchart LR
A[Metric vs rolling baseline] --> B{Breach severity and duration}
B -->|below warning| C[Log only]
B -->|warning, sustained| D[Team channel]
B -->|critical, sustained| E[Page on-call]
E -->|unacknowledged| F[Escalate to secondary]
F -->|still unresolved| G[Escalate to lead]
Trade-offs & pitfalls
- Paging on every metric deviation regardless of duration trains the on-call engineer to ignore pages; requiring a sustained breach costs a little detection latency in exchange for a page people actually trust.
- Static thresholds set once and never revisited go stale as the pipeline's normal volume changes; a baseline needs periodic review, not a permanent fixed number.
- Automating remediation for the wrong failure class (auto-restarting a job that's actually failing because of a genuine data problem, not a transient blip) can mask a real issue for a while and make the eventual page more confusing, not less.
- Mixing infrastructure-health alerts and data-quality alerts into the same tier is a common wrong turn; a resource-saturation page and a null-rate spike usually belong to different owners with different urgency, and combining them routes the wrong person to the wrong problem.
You're migrating an on-prem batch pipeline to a cloud warehouse. Would you keep transforming data before it lands (ETL), or load it raw and transform afterward (ELT), and what would make you choose one over the other here?
Sample Answer
Direct answer
Prefer extract-load-transform (ELT), loading raw and transforming afterward, when the target warehouse's compute is elastic and cheap relative to standing up separate transform infrastructure and you want fast iteration on business logic without redeploying a job. Prefer extract-transform-load (ETL), transforming before landing, when sensitive fields must be redacted or minimized before ever reaching the cloud, the transformation is too stateful or procedural for set-based SQL, or warehouse compute is actually the scarcer, more expensive resource in this environment. The right choice is about where compute is cheapest and where governance has to happen, not a universal best practice.
Structured elaboration
ELT's case. Raw data is preserved for reprocessing, the same decoupling benefit that motivates a bronze layer. Transformation logic lives as versioned SQL close to the consumers who read it, and warehouse compute scales elastically with demand rather than requiring a separately sized, separately operated cluster.
ETL's case. Sensitive fields can be filtered or masked before they ever leave the source environment, which matters when data residency or compliance rules constrain what can land in the cloud at all. Complex, stateful transformation logic that doesn't map cleanly to set-based SQL is often easier to express and test outside the warehouse. And if warehouse compute is priced or provisioned as the expensive resource here, keeping heavy transforms off it can be the cheaper path.
Dual-write during migration. During the transition, the ingestion path writes to both the legacy on-prem target and the new cloud warehouse for a defined window, so both systems stay populated and comparable before anything downstream has cut over. The risk is doubled write cost, and the two paths silently drifting apart if one write fails and isn't retried with identical semantics to the other.
Dual-read during migration. Consumers, or a dedicated validation job, query both the old and new systems for the same request and diff the results before trusting the new path fully. This validates correctness without dual-write's ongoing cost duplication, but it only catches divergence at query time, not continuously between queries.
Typical sequencing. Dual-write while validating that loads land correctly in both places, dual-read while validating that transformed, queryable results match, then cut consumers over to the new path alone and decommission the old system after a retention window.
Worked example
The legacy on-prem batch job transforms 200 GB nightly on already-paid-for dedicated hardware, effectively near-zero marginal cost per run. The ELT alternative lands the same 200 GB raw, then runs a warehouse transform query. Using an illustrative unit price of $5 per terabyte (TB) scanned to show the arithmetic, and assuming the transform's star-schema joins against reference tables cause roughly a 3x scan fan-out over the base 200 GB:
200 GB×3=600 GB=0.6 TB scanned 0.6 TB×$5/TB=$3.00 per run $3.00×30 runs/month=$90/month in warehouse compute for this jobThat ninety dollars a month is the concrete number ELT has to be compared against for whatever the dedicated ETL infrastructure would actually cost to provision and operate for equivalent throughput. If that alternative is cheaper than ninety dollars a month, ETL wins on pure compute-cost grounds; if not, ELT does, independent of any other factor like a redaction requirement, which would settle the choice regardless of the cost comparison.
Trade-offs & pitfalls
Because warehouse pricing is scan-based, ELT's cost is sensitive to query design, an unpartitioned scan or an unnecessarily wide join can blow past what an equivalent ETL job would have cost, in a way a dedicated ETL cluster's roughly fixed cost is not. Dual-write's biggest pitfall is the two paths silently diverging because they don't share identical retry and idempotency semantics, one path retries a failed write and the other doesn't, and now they disagree. A governance pitfall specific to ELT: pushing everything through ELT without column-level masking inside the warehouse means sensitive data sits raw in cloud storage even before anyone has queried it, which widens the blast radius of any breach compared to redacting before load.
| Axis | Favors ETL | Favors ELT |
|---|---|---|
| Sensitive data handling | Redact before it leaves the source | Needs in-warehouse masking to compensate |
| Transformation complexity | Stateful, procedural logic | Set-based, SQL-expressible logic |
| Where compute is cheaper | Dedicated, already-paid-for infrastructure | Elastic, on-demand warehouse compute |
| Iteration speed on business logic | Slower, needs a job redeploy | Fast, versioned SQL close to consumers |
Unlock Full Question Bank
Get access to all 22 Data Pipeline Architecture and Design interview questions and detailed answers.
Sign in to ContinueJoin thousands of developers preparing for their dream job.