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 recurring aggregation job could either fully recompute its output every run, or update just the parts that changed. Walk through how you'd decide between a full recompute and an incremental approach, and what has to be true for the incremental version to be safe.
Sample Answer
Direct answer
Default to full recompute while the dataset is small enough that its cost is negligible and correctness is what you are optimizing for; move to incremental once recompute cost, whether time, compute spend, or contention with other jobs, becomes the actual bottleneck. Incremental is only safe once you can reliably tell exactly what changed since the last run and can apply that change idempotently (a repeated application produces the same result as applying it once); without both of those, incremental will silently drift from what a full recompute would have produced.
Structured elaboration
| Signal | Favors full recompute | Favors incremental |
|---|---|---|
| Data volume vs. change rate | small table, or most rows change on this run anyway | large table where only a small fraction of rows or partitions actually changed |
| Correctness tolerance | any doubt about correctness is expensive (financial reporting), simplicity is worth the cost | incremental output can be validated against periodic full-recompute checkpoints |
| Engineering maturity | new pipeline, no reliable change-tracking yet | change-data-capture (CDC, a mechanism for capturing row-level inserts, updates, and deletes from a source as a stream) or partition-level watermarks already exist and are trusted |
| Late data | rare or never | frequent, with a defined mechanism to mark affected partitions dirty again |
What has to be true for incremental to be safe:
- Deterministic, idempotent transform: reapplying the same incremental step twice on the same input must produce the same output, an upsert or replace-by-key, not a blind increment, or a retried or replayed job double-counts.
- Reliable change detection: you can identify exactly which upstream rows or partitions changed since the last successful run, a watermark, a CDC stream, or an explicit dirty marker, not an approximation.
- A defined reopening path for late data: if a partition already marked done can still receive new data, there has to be a mechanism to mark it dirty again and reprocess it, or incremental quietly stops matching what a full recompute would have produced for that window.
- Periodic reconciliation: because incremental correctness depends on the three points above holding continuously, some periodic full recompute, or at minimum a checksum or row-count comparison against one, has to exist to catch silent drift; incremental without a way to verify against ground truth is running on faith.
Worked example
A table has 500 million total rows. On a typical day, only the newest day's partition changes, plus roughly 0.5% of historical rows get reopened by late-arriving corrections:
0.005×500,000,000=2,500,000 reopened rows
Full recompute cost is proportional to all 500,000,000 rows scanned every run. Incremental cost is proportional to the new day's rows plus the 2,500,000 reopened rows, roughly 0.5% of the full-recompute cost per run once the change-detection and reopening mechanism above are actually in place. That roughly 200x reduction in rows processed per run is the entire argument for going incremental, and it only holds as long as the 0.5% reopened figure is being tracked correctly; if change detection silently misses reopened partitions, the real error compounds run over run instead of staying pinned at 0.5%.
Trade-offs & pitfalls
Adopting incremental before change detection is actually reliable is the most common wrong turn: it looks correct in testing, where late data is rare, and drifts silently in production, where it is not. Treating incremental as strictly better ignores that it adds real engineering surface, dirty-tracking, idempotent upserts, reconciliation jobs, that is not worth it for a small or rarely changing table. Skipping periodic reconciliation against a full recompute removes the only mechanism that catches drift before a stakeholder notices the numbers do not add up.
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.
What's the difference between a data lake and a data warehouse, and how would a pipeline typically use each as it moves data from source to sink?
Sample Answer
Direct answer
A data lake stores raw and semi-structured data cheaply with schema-on-read, optimized for ingest scale and flexible reprocessing; a data warehouse stores curated, modeled data with schema-on-write, optimized for fast, governed, repeated SQL access. In a source-to-sink pipeline, the lake is typically where raw data lands first and stays as the reprocessing archive, while the warehouse (or a warehouse-modeled zone) is the layer BI tools and dashboards actually query.
Structured elaboration
Core distinction
| Aspect | Data lake | Data warehouse |
|---|---|---|
| Schema | Schema-on-read | Schema-on-write |
| Data shape | Raw, semi- or unstructured, any format | Curated, structured, modeled |
| Cost profile | Cheap object storage, compute billed separately | Storage and compute often tuned together for repeat queries |
| Primary consumer | Engineers and scientists, batch reprocessing | Analysts and BI tools, repeated dashboard queries |
| Query performance | Variable, needs a compute engine layered on top | Tuned for fast, repeated, well-modeled queries |
| Governance | Needs active governance or becomes unmanageable | Governance is largely built into the modeled schema |
How a pipeline threads them together
Ingestion lands raw events in the lake first, immutable and partitioned by arrival time, regardless of downstream shape, because that is the cheapest point to keep full fidelity and reprocess later if logic changes. A transformation stage then cleans, conforms, and models that data into the warehouse (or a warehouse-modeled zone), which is what BI and reporting query directly. The lake stays the archive and reprocessing source; the warehouse stays the fast, governed read path. Ad-hoc exploration of raw signals goes to the lake; repeated, latency-sensitive dashboard queries go to the warehouse.
The lakehouse alternative for regulated, audit-heavy settings
In a regulated environment where every read must be auditable and data cannot be duplicated across stores without tight control, a lakehouse pattern collapses the two zones into one: transactional table formats built directly on lake storage add warehouse-like properties (atomicity, consistency, isolation, durability, plus versioning and schema enforcement) to the lake itself, so there is a single lineage instead of two copies to reconcile for an audit. The trade is fewer warehouse-specific conveniences (workload isolation, some query-engine optimizations) in exchange for one authoritative copy.
Worked example
Consider a pipeline handling clickstream and change-data-capture (CDC) feeds from an operational database. Both land in the lake first as immutable, partitioned files, preserving full history even though the operational source only keeps a live snapshot. A daily transform job conforms and models that data into warehouse tables for a daily-active-user dashboard. If a support ticket needs a raw event from three weeks ago that never made it into any warehouse table, the lake still has it; the warehouse never needed to carry that raw volume at all. In a regulated variant of the same pipeline, the raw and modeled tables would instead live as transactional tables on the same lake storage, so the audit trail for "who read what, when" does not have to be reconciled across two separate systems.
Trade-offs & pitfalls
- Treating the lake as if it were queryable at BI speed is a common mistake; a lake needs a compute engine layered on top, and querying raw files directly for dashboards gets slow and expensive fast.
- Skipping the lake and loading straight into the warehouse loses reprocessing ability, forcing a re-pull from the original source if transformation logic ever needs to change.
- Warehouse compute and storage coupling can get expensive at high ingest volume; many pipelines route only aggregated, curated data to the warehouse and leave raw exploration to the lake.
- A lakehouse is not a universal upgrade. It earns its keep specifically when audit simplicity or a hard constraint against duplicating regulated data outweighs the workload-isolation and tuning conveniences a separate warehouse provides.
A stakeholder wants a dashboard that reflects activity within the last few minutes, but you're currently running nightly batch jobs. Would you move the whole pipeline to streaming, keep it batch, or build a hybrid, and what would that hybrid look like?
Sample Answer
Direct answer
Neither a full migration to streaming nor leaving the pipeline as nightly batch is usually right: keep the batch pipeline as the accurate, full-history layer for aggregates that don't need to be fresh, and add a narrow streaming or micro-batch layer that only serves the last few minutes of activity, presented alongside the batch results at the dashboard layer. This mirrors a lambda-style split: completeness comes from batch, freshness comes from a small, disposable fast path.
Structured elaboration
Deciding the split, not just "streaming vs batch"
Start by asking which numbers on the dashboard actually need last-few-minutes freshness. Usually it's a small subset (an "activity right now" tile), while most historical trend lines are fine refreshed nightly. If the real need is a mixed view, a near-real-time operational tile alongside a daily executive summary, design for that multi-tier freshness explicitly: the operational tile gets the streaming path, the executive summary keeps reading from the nightly batch layer, and both are labeled with their own freshness so nobody mistakes a provisional number for the reconciled one.
The hybrid architecture
Ingestion fans the same raw event stream into both paths: continue landing to the existing batch source, and also publish to a durable, ordered log for the fast path. A lightweight streaming or micro-batch job computes short-window aggregates from that log and writes them to a low-latency store the dashboard queries for the "right now" tile; this path is deliberately narrow, covering only the metrics that need sub-hour freshness. The existing nightly batch job keeps computing the full, reconciled aggregates exactly as it does today, feeding historical trend lines and the executive summary. The dashboard layer, not the pipeline, stitches the two together: either as clearly separate panels with different "as of" timestamps, or by having the fast path's numbers get superseded once batch recomputes the same period, so there is a single definition of "final" per period.
flowchart LR
Src[Event source] --> Batch[Nightly batch pipeline]
Src --> Stream[Streaming or micro-batch layer]
Batch --> Historical[(Historical aggregates)]
Stream --> Recent[(Recent-minutes aggregates)]
Historical --> Dash[Dashboard: unified view]
Recent --> Dash
Sizing the fast path
If the source runs at roughly billion-scale volume per day, the fast path has to be sized for sustained throughput, not just for how often the dashboard is queried.
Worked example
86,400 s1,000,000,000 events≈11,574 events/sec average
11,574×5≈57,870 events/sec at a 5x peak-to-average ratio
That average-to-peak sizing determines whether the fast path needs a horizontally scaled stream processor or whether a simpler scheduled micro-batch clears the bar. A stakeholder asking for "the last few minutes" rarely needs true per-event streaming; a micro-batch cadence of one to two minutes usually satisfies that freshness bar at a fraction of the operational cost of a fully event-driven path, and is the first thing worth trying before committing to full streaming infrastructure.
Trade-offs & pitfalls
- Migrating the entire pipeline to streaming to fix one dashboard tile multiplies operational surface (state management, backpressure, monitoring) for freshness the majority of metrics never asked for.
- Serving the fast path's numbers without labeling them as provisional costs trust: when the nightly batch later reconciles the same period and the number moves, stakeholders need to have been told upfront that the recent window was an estimate.
- Running two computation paths, even a narrow fast path alongside the existing batch path, reintroduces lambda-style risk of logic drift between them; keep the fast-path aggregation as close as possible to the batch definitions to avoid two different answers for the same hour.
- Sizing the fast path for peak load only, or average load only, is a common wrong turn; design for the peak-to-average ratio, since a fast path that falls behind during a burst defeats the purpose of the freshness guarantee.
What is a dead-letter queue, and what problem does it actually solve for a pipeline that has to keep moving even when a few records fail?
Sample Answer
Direct answer
A dead-letter queue (DLQ) is a separate holding queue where a pipeline puts a record it couldn't successfully process, instead of blocking the whole run on it, silently dropping it, or crashing. It solves the problem of a pipeline needing to keep moving for the overwhelming majority of records that are fine, without losing the small number that aren't.
Structured elaboration
Why this is needed at all. Without somewhere to put a bad record, a processing stage has only two options when it hits one: stop everything until a human intervenes, which halts every good record queued behind it too, or silently skip and drop the bad one, losing it without a trace. Both are worse than isolating it.
What actually goes into it. Not just the failed record itself, but enough context to act on it later: the original payload, the reason it failed, when it failed, and how many times it was tried before landing here.
What a dead-letter queue is not. It is not a fix, and it is not the same as an error log. A queue nobody ever reads or reprocesses is functionally the same as silently dropping records, just with extra storage cost attached.
What has to exist alongside it for it to actually work. A way to notice it's growing, so a genuine upstream break doesn't go unnoticed for days, and a way to get corrected records back into the main flow. Without both, isolating the failure doesn't actually solve anything, it just relocates the problem.
Worked example
A pipeline processes 10,000 records in a run, and 7 of them fail parsing because of a malformed field:
10,0007=0.07%Without a dead-letter queue, the pipeline either stops the entire run of 10,000 records for those 7, or silently proceeds with the other 9,993 and nobody ever learns the 7 existed. With a dead-letter queue, the 9,993 good records finish normally, and the 7 are isolated along with their failure reasons, so someone can look at exactly those 7 without slowing down the other 99.93 percent of the batch.
Trade-offs & pitfalls
- A dead-letter queue with no monitoring on its growth rate can hide a real, ongoing upstream problem for a long time; a slow trickle and a growing crisis look identical if nobody's watching the rate.
- A dead-letter queue with no path back into the main flow is a graveyard, not a safety valve; isolating failures only pays off if corrected records can actually rejoin the pipeline.
- Using it as a catch-all for any exception, including genuine bugs in the pipeline's own logic, can mask defects that should fail loudly during development instead of quietly accumulating in production.
- It's built for isolating a minority of individually bad records; a systemic failure hitting most or all records calls for stopping the pipeline entirely, not queuing everything that comes through.
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.