Data Engineer Interview Questions
Core Overview
Prepare for Data Engineer interviews covering data modeling, ETL, Apache Spark, SQL query optimization, and streaming data.
Ready to test your knowledge?
Launch a focused practice session to review questions without distraction.
What is the difference between OLTP systems, OLAP systems, and a data warehouse?
Direct Answer
OLTP systems process operational transactions, while OLAP systems and data warehouses organize historical data for analytical queries, reporting, and decision-making.
Detailed Explanation
Online Transaction Processing, or OLTP, systems support the day-to-day operations of an application.
Typical OLTP workloads include:
OLTP systems generally prioritize:
Online Analytical Processing, or OLAP, systems support analytical questions across larger volumes of historical data.
Examples include:
OLAP workloads commonly perform scans, joins, aggregations, filtering, and time-series comparisons across many rows.
A data warehouse is a centralized analytical data store that integrates data from operational databases, applications, files, event streams, and third-party systems.
Warehouse data is normally transformed into stable analytical structures so reporting tools and analysts do not depend directly on operational schemas.
Separating transactional and analytical workloads prevents expensive reporting queries from competing with production transactions. It also allows analytical history to remain available even when the operational system retains only current state.
A warehouse is not automatically the authoritative source for every business operation. The operational application normally remains authoritative for transactional state, while the warehouse becomes authoritative for defined analytical datasets and metrics.
Code Example
-- OLTP query: retrieve one current application.
SELECT
id,
user_id,
company_name,
status,
updated_at
FROM job_applications
WHERE id = $1
AND user_id = $2;
-- OLAP query: analyze monthly outcomes.
SELECT
DATE_TRUNC('month', created_at) AS month,
source,
COUNT(*) AS applications,
COUNT(*) FILTER (
WHERE status = 'interview'
) AS interviews,
COUNT(*) FILTER (
WHERE status = 'offer'
) AS offers
FROM analytics.fact_job_applications
GROUP BY
DATE_TRUNC('month', created_at),
source
ORDER BY month, source;Common Interview Pitfalls
- Running large analytical scans directly against a latency-sensitive production database.
- Assuming OLTP and OLAP systems have identical schema and performance requirements.
- Treating the data warehouse as the source of truth for operational writes.
- Copying operational tables into a warehouse without defining analytical meaning.
- Ignoring historical data because the operational system stores only current state.
- Using the term database and data warehouse as though they always describe the same workload.
What are fact tables, dimension tables, grain, and a star schema?
Direct Answer
Fact tables record measurable business events at a defined grain, while dimension tables provide descriptive context and connect to facts in a star-shaped analytical model.
Detailed Explanation
A dimensional model organizes analytical data around business processes and the questions users need to answer.
A fact table records events, measurements, or process states.
Examples include:
Facts may contain:
A dimension table describes the entities associated with facts.
Examples include:
The grain defines exactly what one row in a fact table represents. Grain should be stated before selecting measures or dimensions.
For example:
> One row represents one user submission of one job application at one point in time.
Mixing several grains in one fact table creates ambiguous metrics and duplication. A table containing both application-level rows and individual status-change rows would make simple application counts incorrect.
A star schema contains a central fact table connected directly to denormalized dimension tables. It provides understandable joins and supports common filtering and aggregation patterns.
A snowflake-style schema normalizes some dimension attributes into additional tables. This may reduce duplication, but it also adds joins and semantic complexity.
The best model is determined by business meaning, data volume, query patterns, governance, and the expectations of downstream analytical tools—not simply by choosing the schema with the fewest tables.
Code Example
CREATE TABLE analytics.dim_company (
company_key BIGINT PRIMARY KEY,
company_id TEXT NOT NULL,
company_name TEXT NOT NULL,
industry TEXT,
company_size TEXT
);
CREATE TABLE analytics.dim_date (
date_key INTEGER PRIMARY KEY,
calendar_date DATE NOT NULL UNIQUE,
calendar_year INTEGER NOT NULL,
calendar_month INTEGER NOT NULL,
month_name TEXT NOT NULL
);
CREATE TABLE analytics.fact_job_application (
application_key BIGINT PRIMARY KEY,
application_id TEXT NOT NULL,
user_key BIGINT NOT NULL,
company_key BIGINT NOT NULL,
submitted_date_key INTEGER NOT NULL,
applications_count INTEGER NOT NULL DEFAULT 1,
days_to_interview INTEGER,
FOREIGN KEY (company_key)
REFERENCES analytics.dim_company(company_key),
FOREIGN KEY (submitted_date_key)
REFERENCES analytics.dim_date(date_key)
);
-- Grain:
-- One row per submitted job application.Common Interview Pitfalls
- Creating a fact table without explicitly defining its grain.
- Mixing event-level and daily aggregate rows in the same fact table.
- Placing descriptive text attributes repeatedly in a large fact table without analysis.
- Counting joined rows without considering one-to-many relationships.
- Assuming every numeric column is an additive fact.
- Designing dimensions around source-system tables instead of analytical business meaning.
- Using a star schema while leaving important dimensions without stable keys.
How do normalization and denormalization differ, and when should each be used?
Direct Answer
Normalization separates data to reduce redundancy and update anomalies, while denormalization duplicates or combines data to simplify reads and improve selected analytical access patterns.
Detailed Explanation
Normalization organizes relational data so that each fact is represented in an appropriate place and dependencies are expressed through keys and relationships.
Benefits include:
For example, storing a company name once in a company table prevents every application row from needing an update when the company name changes.
Normalization levels are described through normal forms. In practice, transactional systems commonly aim for designs where non-key attributes depend on the correct key and repeating groups or partial dependencies are removed.
Denormalization intentionally combines or duplicates data to optimize specific read patterns.
Examples include:
Denormalization can reduce joins and repeated computation, but it introduces costs:
Transactional schemas are often more normalized because they handle frequent changes and require consistent writes. Analytical models are often more denormalized because they prioritize understandable, efficient queries across historical data.
The decision should be driven by correctness and measured workload requirements. Denormalization should not be used as a substitute for missing index, inefficient queries, or unclear modeling.
Code Example
-- Normalized operational model.
CREATE TABLE companies (
id UUID PRIMARY KEY,
name TEXT NOT NULL,
industry TEXT
);
CREATE TABLE job_applications (
id UUID PRIMARY KEY,
company_id UUID NOT NULL
REFERENCES companies(id),
user_id UUID NOT NULL,
status TEXT NOT NULL
);
-- Denormalized analytical output.
CREATE MATERIALIZED VIEW analytics.application_summary AS
SELECT
a.id AS application_id,
a.user_id,
a.status,
c.id AS company_id,
c.name AS company_name,
c.industry
FROM job_applications AS a
JOIN companies AS c
ON c.id = a.company_id;Common Interview Pitfalls
- Denormalizing data before identifying an actual query or performance requirement.
- Duplicating attributes without defining how copied values remain consistent.
- Normalizing analytical models so aggressively that basic reporting requires excessive joins.
- Using denormalization to hide incorrect relationships or ambiguous grain.
- Assuming normalization always produces faster queries.
- Updating historical analytical values when the model is intended to preserve event-time context.
- Creating wide tables without documenting ownership and refresh behavior.
How do joins, aggregations, common table expressions, and window functions support analytical SQL?
Direct Answer
Joins combine related rows, aggregations collapse groups, CTEs organize query stages, and window functions calculate across related rows without removing row-level detail.
Detailed Explanation
Analytical SQL often combines several operations.
Joins combine rows from related datasets.
INNER JOIN returns matching rows.LEFT JOIN preserves rows from the left side when no match exists.FULL JOIN preserves unmatched rows from both sides.EXISTS.NOT EXISTS.Join cardinality must be understood. Joining one application to several status events creates several output rows. Aggregating after that join may overcount applications.
Aggregations reduce rows into groups using functions such as:
COUNTSUMAVGMINMAXEvery selected nonaggregated expression must be compatible with the grouping logic.
A common table expression, or CTE, names a query stage. CTEs can improve readability and support recursive queries, though they should not be assumed to improve performance automatically.
A window function calculates across a set of related rows while retaining each row.
Common examples include:
ROW_NUMBER for deterministic ranking or deduplicationLAG and LEAD for comparing adjacent eventsWindow functions use PARTITION BY to define groups and ORDER BY to define sequence. A deterministic order should include a tie-breaker when timestamps are not unique.
Code Example
WITH ordered_events AS (
SELECT
application_id,
status,
occurred_at,
event_id,
LAG(status) OVER (
PARTITION BY application_id
ORDER BY occurred_at, event_id
) AS previous_status,
ROW_NUMBER() OVER (
PARTITION BY application_id
ORDER BY occurred_at DESC, event_id DESC
) AS latest_rank
FROM application_status_events
),
latest_application_status AS (
SELECT
application_id,
status,
occurred_at
FROM ordered_events
WHERE latest_rank = 1
)
SELECT
status,
COUNT(*) AS applications
FROM latest_application_status
GROUP BY status
ORDER BY applications DESC;Common Interview Pitfalls
- Using an inner join when unmatched rows must remain in the result.
- Overcounting entities after joining to a one-to-many table.
- Using NOT IN with nullable values without considering three-valued logic.
- Applying a window function without deterministic ordering.
- Using DISTINCT to conceal an incorrect join rather than fixing the relationship.
- Grouping at a different grain from the metric being calculated.
- Assuming a common table expression always improves query performance.
- Filtering rows before a window calculation when the excluded rows are needed for context.
How do surrogate keys, slowly changing dimensions, and data-quality constraints preserve analytical history?
Direct Answer
Surrogate keys identify warehouse records independently from source keys, while slowly changing dimension strategies control whether attribute changes overwrite or preserve history.
Detailed Explanation
A natural key is a business or source-system identifier, such as a company ID, account number, or product code.
A surrogate key is a warehouse-generated identifier used to identify a dimension row independently of the source-system key.
Surrogate keys are useful because:
A slowly changing dimension, or SCD, defines how changes to dimension attributes are represented.
Common approaches include:
Type 1 is appropriate when history is unnecessary or the old value was incorrect. Type 2 is appropriate when analysis must use the attributes that were effective when an event occurred.
For a Type 2 dimension, the natural key can appear in several rows, but each row has a different surrogate key and non-overlapping validity period.
Data-quality protections may include:
Late-arriving data requires an explicit strategy. A fact may temporarily reference an unknown dimension row, or the pipeline may defer the fact until the dimension becomes available.
Code Example
CREATE TABLE analytics.dim_company (
company_key BIGINT GENERATED ALWAYS AS IDENTITY
PRIMARY KEY,
company_id TEXT NOT NULL,
company_name TEXT NOT NULL,
industry TEXT,
valid_from TIMESTAMPTZ NOT NULL,
valid_to TIMESTAMPTZ,
is_current BOOLEAN NOT NULL,
CHECK (
valid_to IS NULL OR
valid_to > valid_from
)
);
CREATE UNIQUE INDEX one_current_company_version
ON analytics.dim_company(company_id)
WHERE is_current;
-- Type 2 update:
-- 1. Expire the current row.
UPDATE analytics.dim_company
SET
valid_to = $2,
is_current = FALSE
WHERE company_id = $1
AND is_current = TRUE;
-- 2. Insert the new version.
INSERT INTO analytics.dim_company (
company_id,
company_name,
industry,
valid_from,
valid_to,
is_current
)
VALUES ($1, $3, $4, $2, NULL, TRUE);Common Interview Pitfalls
- Using a changing source identifier as the only warehouse dimension key.
- Applying Type 2 history to every attribute without a business requirement.
- Overwriting historical attributes that reports are expected to preserve.
- Creating overlapping validity periods for one natural key.
- Allowing several current dimension rows for the same natural entity.
- Joining facts to the current dimension row instead of the historically applicable version.
- Failing to define how late-arriving dimensions or facts are handled.
- Treating every source-system change as a valid analytical update.
How would you design an analytics warehouse for job discovery, application tracking, subscriptions, and product engagement?
Direct Answer
Define business processes and grain, ingest immutable source data, create tested dimensions and facts, standardize metrics, protect sensitive data, and support incremental reliable processing.
Detailed Explanation
A warehouse design should begin with business questions, source-system behavior, data ownership, freshness requirements, and metric definitions.
For a career platform, major analytical processes may include:
Source and ingestion layer
Extract operational database changes, product events, billing events, and external job-source data into a durable raw layer.
Raw records should retain:
The raw layer makes reprocessing possible when transformation logic changes.
Backup & Modeling
Define the grain of each fact table before selecting dimensions and measures.
Potential facts include:
Shared dimensions may include user, date, company, job, acquisition source, plan, role, and geography.
History
Use event facts for state transitions and Type 2 dimensions only where historical attribute changes affect analysis. Do not rely solely on current operational status when funnel progression must be reconstructed.
Metric governance
Define metrics centrally, including numerator, denominator, grain, event-time field, exclusions, timezone, and late-data policy.
For example, application-to-interview conversion must specify whether it measures applications, users, or companies and which time cohort owns the outcome.
Incremental processing
Transform only new or changed records where practical. Pipelines must tolerate retries, duplicates, late arrivals, out-of-order events, and corrected source data.
Use stable business or event keys and deterministic merge behavior.
Data quality
Validate:
Security and privacy
Separate direct personal information from broadly accessible analytical models. Apply role-based access, column or row controls, masking, retention rules, and audit logging.
Analysts should not need access to resume content, authentication secrets, or complete user profiles to calculate product metrics.
Performance
Optimize physical design from measured workloads. Use partitioning, clustering, materialized outputs, or aggregate tables where the warehouse supports them and query evidence justifies them.
The final architecture should make business meaning explicit, allow historical reprocessing, and prevent different teams from calculating the same metric through incompatible logic.
Code Example
-- Example event fact at a stable grain.
CREATE TABLE analytics.fact_application_status_event (
status_event_key BIGINT PRIMARY KEY,
status_event_id TEXT NOT NULL UNIQUE,
application_id TEXT NOT NULL,
user_key BIGINT NOT NULL,
job_key BIGINT NOT NULL,
company_key BIGINT NOT NULL,
status TEXT NOT NULL,
occurred_at TIMESTAMPTZ NOT NULL,
ingested_at TIMESTAMPTZ NOT NULL,
source_system TEXT NOT NULL,
CHECK (
status IN (
'saved',
'applied',
'screening',
'interview',
'offer',
'rejected',
'withdrawn'
)
)
);
-- Cohort-based conversion.
WITH application_cohort AS (
SELECT
application_id,
user_key,
MIN(occurred_at) FILTER (
WHERE status = 'applied'
) AS applied_at,
MIN(occurred_at) FILTER (
WHERE status = 'interview'
) AS first_interview_at
FROM analytics.fact_application_status_event
GROUP BY application_id, user_key
)
SELECT
DATE_TRUNC('month', applied_at) AS cohort_month,
COUNT(*) AS applications,
COUNT(*) FILTER (
WHERE first_interview_at IS NOT NULL
) AS applications_with_interview,
COUNT(*) FILTER (
WHERE first_interview_at IS NOT NULL
)::NUMERIC /
NULLIF(COUNT(*), 0) AS interview_conversion
FROM application_cohort
WHERE applied_at IS NOT NULL
GROUP BY DATE_TRUNC('month', applied_at);Common Interview Pitfalls
- Starting warehouse design from existing source tables instead of business processes and grain.
- Keeping only transformed tables and making historical reprocessing impossible.
- Building funnel metrics from current status instead of historical status events.
- Allowing different dashboards to define the same conversion metric differently.
- Using ingestion time when business analysis requires event time.
- Ignoring duplicate, corrected, late, or out-of-order source records.
- Giving broad analytical users access to unnecessary personal or sensitive data.
- Optimizing partitions and clustering before measuring actual query patterns.
- Creating one universal wide table with several incompatible grains.
- Failing to reconcile warehouse counts with authoritative source systems.
What is the difference between ETL and ELT, and what stages commonly exist in a batch data pipeline?
Direct Answer
ETL transforms data before loading it into the target, while ELT loads raw data first and performs transformations within the analytical platform.
Detailed Explanation
ETL means extract, transform, and load.
In an ETL workflow:
1. Data is extracted from one or more sources.
2. It is validated, cleaned, joined, standardized, or aggregated in a processing layer.
3. The transformed result is loaded into the destination.
ETL is useful when the destination should receive only validated and shaped data, when transformation must occur outside the warehouse, or when the target has limited transformation capability.
ELT means extract, load, and transform.
In an ELT workflow:
1. Source data is extracted.
2. Raw or lightly normalized data is loaded into an analytical platform.
3. Transformations run inside that platform using its compute engine.
ELT can preserve source history and make reprocessing easier because transformation logic can be rerun against retained raw data.
A production batch pipeline commonly includes several logical layers:
ETL and ELT are architectural patterns rather than strict opposites. A pipeline may perform lightweight extraction-time validation, load raw data, and then perform most business transformations in the warehouse.
The important design questions are where data is retained, where transformations execute, how failures are recovered, and whether historical source data can be reprocessed.
Code Example
-- Raw layer: retain source-aligned records.
CREATE TABLE raw.job_application_events (
source_event_id TEXT NOT NULL,
source_payload JSONB NOT NULL,
source_updated_at TIMESTAMPTZ,
ingested_at TIMESTAMPTZ NOT NULL,
ingestion_batch_id TEXT NOT NULL
);
-- Staging layer: parse and standardize.
CREATE VIEW staging.job_application_events AS
SELECT
source_event_id,
source_payload ->> 'application_id'
AS application_id,
LOWER(source_payload ->> 'status')
AS normalized_status,
source_updated_at,
ingested_at
FROM raw.job_application_events;
-- Serving layer: business-facing output.
CREATE TABLE analytics.fact_application_status_event (
status_event_id TEXT PRIMARY KEY,
application_id TEXT NOT NULL,
status TEXT NOT NULL,
occurred_at TIMESTAMPTZ NOT NULL
);Common Interview Pitfalls
- Using ETL and ELT as interchangeable terms without identifying where transformation occurs.
- Discarding raw source data and making corrected transformations impossible to rerun.
- Applying business logic directly during extraction without retaining source evidence.
- Treating the landing layer as a trusted analytics dataset.
- Mixing source ingestion, business transformation, and publication in one opaque task.
- Assuming ELT eliminates the need for validation and orchestration.
What is the difference between a full load and an incremental load, and how are watermarks used?
Direct Answer
A full load processes the complete source dataset, while an incremental load processes records added or changed since a recorded boundary such as a watermark.
Detailed Explanation
A full load reads and processes the complete eligible source dataset during every run.
Full loads are simple to reason about and can be appropriate when:
Their cost increases as data volume grows, and repeatedly scanning or replacing large datasets can consume substantial source, network, compute, and warehouse resources.
An incremental load processes only data that is new or changed since a previous successful boundary.
Incremental boundaries may use:
A watermark records progress through the source. For example, the pipeline may store the greatest successfully processed updated_at timestamp.
Timestamp-only watermarks require care because:
A safer boundary may use a composite key such as (updated_at, source_id) and an overlap window. Records within the overlap are reread and merged idempotently.
The watermark should advance only after the destination write and required validation complete successfully. Advancing it before a failed write can permanently skip data.
Incremental loading reduces work, but it increases state-management, late-data, deletion, correction, and recovery complexity.
Code Example
-- Read from the last successful composite watermark.
SELECT
source_id,
application_id,
status,
updated_at
FROM source.application_events
WHERE
(updated_at, source_id) >
(:last_updated_at, :last_source_id)
ORDER BY updated_at, source_id
LIMIT 10000;
-- Merge records idempotently.
INSERT INTO staging.application_events (
source_id,
application_id,
status,
updated_at
)
VALUES (
:source_id,
:application_id,
:status,
:updated_at
)
ON CONFLICT (source_id)
DO UPDATE SET
application_id = EXCLUDED.application_id,
status = EXCLUDED.status,
updated_at = EXCLUDED.updated_at;Common Interview Pitfalls
- Advancing the watermark before destination writes complete successfully.
- Using only a non-unique timestamp and skipping rows with equal timestamps.
- Assuming source records always arrive in timestamp order.
- Ignoring updates and deletions after an initial insert is processed.
- Using an incremental load without a method to rebuild from source history.
- Rereading overlapping data without using idempotent destination logic.
- Storing pipeline progress only in transient worker memory.
How should a batch data pipeline be designed so retries do not create duplicates or inconsistent outputs?
Direct Answer
Use stable record and batch identifiers, deterministic transformations, atomic publication, merge semantics, and retry-safe checkpoints so repeated execution produces the same result.
Detailed Explanation
A pipeline is idempotent when executing the same logical input more than once produces the same intended destination state as executing it once.
Retries are normal in distributed systems. A task may complete its destination write but fail before reporting success, causing the orchestrator to run it again.
Idempotent design techniques include:
An append-only pipeline needs a durable event identifier or another deduplication strategy. Comparing complete payloads is often unreliable because formatting or metadata may change even when the logical event is the same.
Batch-level idempotency may use a deterministic output path such as:
analytics/application_events/event_date=2026-08-05
The task can replace or merge only that partition rather than appending another copy on every retry.
Exactly-once processing claims must be interpreted end to end. A processing engine may provide exactly-once state handling, but an external API, email system, or nontransactional destination can still receive duplicate side effects.
Retries should distinguish transient failures from invalid data. Retrying malformed input repeatedly wastes resources and may block valid records. Invalid records can be quarantined with sufficient context for correction and replay.
Code Example
BEGIN;
CREATE TEMP TABLE incoming_events (
event_id TEXT PRIMARY KEY,
application_id TEXT NOT NULL,
status TEXT NOT NULL,
occurred_at TIMESTAMPTZ NOT NULL
) ON COMMIT DROP;
-- Load the current batch into incoming_events.
INSERT INTO analytics.application_status_events (
event_id,
application_id,
status,
occurred_at
)
SELECT
event_id,
application_id,
status,
occurred_at
FROM incoming_events
ON CONFLICT (event_id)
DO UPDATE SET
application_id = EXCLUDED.application_id,
status = EXCLUDED.status,
occurred_at = EXCLUDED.occurred_at;
INSERT INTO pipeline.processed_batches (
batch_id,
completed_at
)
VALUES (:batch_id, NOW())
ON CONFLICT (batch_id) DO NOTHING;
COMMIT;Common Interview Pitfalls
- Appending every retry without a stable event or business key.
- Marking a batch successful before destination publication completes.
- Using nondeterministic timestamps or random identifiers during transformations.
- Assuming an orchestration retry begins before every prior side effect.
- Retrying permanently invalid records without quarantine or limits.
- Deleting and rebuilding an entire shared table for one failed partition.
- Claiming exactly-once behavior without evaluating external destination semantics.
How should an Airflow DAG model task dependencies, data intervals, retries, and operational limits?
Direct Answer
A DAG defines dependency order and scheduling, while tasks should process explicit data intervals, use bounded retries, declare resources, and remain independently observable.
Detailed Explanation
An Airflow DAG describes a workflow as tasks and dependencies without requiring the scheduler to understand the internal business logic of each task.
A well-designed DAG should make the following explicit:
The logical date or data interval is not necessarily the wall-clock time at which the task starts. A daily run may begin after the interval closes and should process the interval assigned by the scheduler.
Tasks should derive input partitions from the run’s data interval rather than calling now() and guessing which date to process. This makes scheduled runs, retries, and historical backfills consistent.
Retries should be bounded and appropriate for transient errors such as temporary network or service failures. A retry is not a substitute for fixing deterministic validation errors.
Tasks should be idempotent because Airflow may retry them or an operator may clear and rerun them.
Large datasets should not be passed through Airflow metadata or inter-task messages. Tasks should exchange durable references such as object-storage paths, table partitions, or batch identifiers.
Concurrency controls can prevent a backfill or delayed schedule from overwhelming a database, API, or warehouse. Pools can limit access to shared external resources independently from total worker capacity.
A DAG should represent meaningful data dependencies. Adding artificial sequential dependencies reduces parallelism and can increase recovery time.
Code Example
from datetime import datetime, timedelta
from airflow import DAG
from airflow.decorators import task
with DAG(
dag_id="daily_application_warehouse",
start_date=datetime(2026, 1, 1),
schedule="@daily",
catchup=True,
max_active_runs=2,
default_args={
"retries": 3,
"retry_delay": timedelta(minutes=5),
},
) as dag:
@task(execution_timeout=timedelta(minutes=30))
def extract(data_interval_start, data_interval_end):
return {
"start": data_interval_start.isoformat(),
"end": data_interval_end.isoformat(),
"manifest":
"s3://data/raw/application-events/"
}
@task
def transform(extraction):
return {
"start": extraction["start"],
"end": extraction["end"]
}
@task
def validate(output):
pass
validate(transform(extract()))Common Interview Pitfalls
- Using the task start time instead of the scheduled data interval.
- Passing large datasets through orchestration metadata.
- Retrying deterministic schema or validation errors repeatedly.
- Creating tasks that produce duplicate output when cleared and rerun.
- Allowing unlimited concurrent backfill runs against a shared source.
- Making every task sequential even when dependencies allow parallel execution.
- Using the orchestration database as the analytical data store.
- Marking a task successful before its published data passes validation.
How should a data team perform backfills while handling late-arriving data and source-schema evolution?
Direct Answer
Backfills should reprocess bounded historical intervals with versioned logic, controlled concurrency, idempotent writes, schema compatibility, validation, and publication safeguards.
Detailed Explanation
A backfill reprocesses historical data intervals. Common reasons include:
A backfill should define:
Backfills should use the same logical interval semantics as scheduled runs. Running the current pipeline with now()-based filters can process the wrong data.
Large backfills can overload operational databases, warehouses, APIs, and orchestration systems. Concurrency should be bounded independently from ordinary scheduled processing where possible.
Late-arriving data is data that becomes available after its expected processing interval. The system may handle it through:
The policy should state how long historical results remain open to change.
Schema evolution occurs when source fields are added, removed, renamed, or change type or meaning.
Safe handling includes:
A backfill should not silently replace trusted production data. Write to isolated tables or partitions, compare quality and reconciliation results, and then publish through a controlled swap or merge.
Code Example
# Controlled historical reprocessing.
airflow dags backfill
--dag-id daily_application_warehouse
--start-date 2026-05-01
--end-date 2026-05-31Common Interview Pitfalls
- Running an unrestricted backfill that overwhelms production sources or the warehouse.
- Using current wall-clock time instead of each historical data interval.
- Writing directly over trusted production partitions before validation.
- Assuming late-arriving records affect only the latest partition.
- Parsing every historical record with assumptions from only the newest source schema.
- Reprocessing data without recording the transformation-code version.
- Running backfills concurrently with scheduled jobs that write the same partitions.
- Changing business definitions historically without communicating metric restatement.
How would you design a reliable batch data platform that ingests operational databases, files, APIs, and product events into an analytics warehouse?
Direct Answer
Use durable raw ingestion, explicit intervals, idempotent transformations, isolated orchestration, tested publication, lineage, observability, backfill support, and governed access.
Detailed Explanation
A reliable batch platform must support normal schedules, retries, historical replay, schema changes, source outages, late data, and business-definition changes without silently corrupting trusted analytics.
Source ingestion
Use appropriate extraction methods for each source:
Avoid repeatedly scanning operational systems when a log-based or incremental source is available and trustworthy.
Raw storage
Land immutable or append-only source-aligned data before business transformation.
Include metadata such as:
Partition raw data using a strategy that supports retrieval and replay without relying solely on arrival time for business meaning.
Transformation architecture
Separate parsing, standardization, conformance, business modeling, and serving outputs.
Transformations should be deterministic and idempotent. Use stable keys, destination constraints, partition replacement, or merge behavior according to the model.
Orchestration
Workflows should process explicit data intervals and declare dependencies, retries, timeouts, concurrency limits, and ownership.
Use resource pools or environment limits so backfills and delayed workloads do not overwhelm shared databases, APIs, or warehouses.
Data quality and publication
Apply checks at multiple stages:
Failed data should not automatically replace a trusted production dataset. Publish only after required checks pass.
Observability
Track:
Schema evolution and contracts
Record source schema versions and define whether changes are additive, compatible, deprecated, or breaking. Alert owners before downstream data is silently dropped or misinterpreted.
Recovery and backfills
Retain enough raw history and code version information to reproduce outputs. Backfills should write to isolated destinations, use controlled concurrency, and support reconciliation before publication.
Security and governance
Classify data at ingestion. Restrict personal, credential, financial, and document content. Apply masking, retention, encryption, audit logging, and least-privilege access.
Operational separation
The orchestrator coordinates work but should not become the storage layer for datasets. Compute workers should be replaceable, while durable data, manifests, checkpoints, and publication metadata live in systems designed for persistence.
The platform should optimize first for correctness, replayability, and visibility. Performance optimization is valuable only after the system can prove what it processed and reproduce its outputs.
Code Example
type BatchManifest = {
batchId: string;
source: string;
intervalStart: string;
intervalEnd: string;
schemaVersion: string;
extractionPosition: string;
inputFiles: Array<{
uri: string;
checksum: string;
recordCount: number;
}>;
transformationVersion: string;
};
type PublicationResult = {
batchId: string;
outputDataset: string;
outputPartition: string;
sourceRecords: number;
publishedRecords: number;
rejectedRecords: number;
qualityChecksPassed: string[];
publishedAt: string;
};
async function publishBatch(
manifest: BatchManifest
): Promise<PublicationResult> {
await validateManifest(manifest);
const staged = await transformToStaging(
manifest
);
const quality = await runQualityChecks(
staged
);
if (!quality.passed) {
throw new Error(
'Batch failed publication checks'
);
}
return publishPartitionAtomically(
manifest,
staged,
quality
);
}Common Interview Pitfalls
- Transforming data without retaining a durable replayable raw layer.
- Using ingestion time as the only boundary for event-time analytics.
- Allowing failed quality checks to publish incomplete production data.
- Keeping extraction checkpoints only inside temporary worker processes.
- Allowing backfills and schedules to overwrite the same partition concurrently.
- Treating every source-schema change as automatically compatible.
- Using the orchestration metadata database to transfer large datasets.
- Collecting pipeline success metrics without measuring data freshness or completeness.
- Giving broad warehouse access to sensitive raw source content.
- Designing for maximum throughput before establishing reproducibility and correctness.
How does Apache Spark distribute a data-processing job across a cluster?
Direct Answer
A Spark driver builds and schedules work, executors run tasks over data partitions, and the cluster manager allocates the resources used by the application.
Detailed Explanation
A Spark application distributes data and computation across a cluster.
The major components are:
When an action requires computation, Spark creates a job. The job is divided into stages according to dependencies and shuffle boundaries. Each stage contains tasks that operate on partitions.
Parallelism is bounded by the number of available partitions and executor resources. A dataset with only two partitions cannot use hundreds of task slots effectively for that stage.
The driver must remain healthy because it coordinates the application and stores execution metadata. Collecting a large distributed result into the driver can exhaust its memory.
Executors are replaceable. When an executor fails, Spark may recompute lost partition output from lineage or rerun affected tasks, provided the source and intermediate dependencies remain available.
The architecture should separate application correctness from cluster size. Adding executors may increase throughput, but it cannot fix an inefficient plan, severe data skew, a single-partition operation, or an overloaded destination.
Code Example
from pyspark.sql import SparkSession
spark = (
SparkSession.builder
.appName("daily-application-metrics")
.getOrCreate()
)
applications = spark.read.parquet(
"s3://analytics/raw/job-applications/"
)
print(
applications.rdd.getNumPartitions()
)
monthly = (
applications
.groupBy("application_month", "source")
.count()
)
monthly.write.mode("overwrite").parquet(
"s3://analytics/marts/monthly-applications/"
)Common Interview Pitfalls
- Treating the driver and executors as though they perform identical responsibilities.
- Assuming additional executors improve a stage with very few partitions.
- Collecting a large distributed dataset into driver memory.
- Treating executor loss as equivalent to permanent loss of the original source data.
- Increasing cluster size before inspecting the execution plan and data distribution.
- Running unrelated Spark applications through one unrestricted shared resource pool.
What are Spark DataFrames, transformations, actions, and lazy evaluation?
Direct Answer
A DataFrame represents distributed structured data, transformations build a logical plan lazily, and actions trigger Spark to optimize and execute that plan.
Detailed Explanation
A Spark DataFrame represents distributed structured data organized into named columns and a schema.
DataFrames allow Spark SQL to understand column types, expressions, filters, joins, and aggregations. Spark can therefore optimize the execution plan rather than treating every operation as opaque user code.
A transformation defines a new dataset without immediately executing the complete computation.
Examples include:
selectfilterwithColumnjoingroupByrepartitionAn action requires Spark to produce a result or write output.
Examples include:
countcollectshowtakeSpark uses lazy evaluation. Transformations build a logical plan. When an action occurs, Spark analyzes and optimizes the plan, produces a physical plan, divides it into stages, and schedules tasks.
Lazy evaluation can combine operations, push filters closer to data sources, remove unused columns, and choose more efficient physical strategies.
The execution plan can be inspected with explain. Engineers should use the plan to confirm filter pushdown, join selection, exchanges, scans, and partition behavior.
DataFrames are normally preferred for structured workloads because they use Spark SQL’s optimization capabilities. RDDs remain useful when the data or processing cannot be expressed effectively through structured APIs, but low-level RDD transformations provide less relational information to the optimizer.
Calling multiple actions on the same uncached DataFrame may recompute its lineage each time.
Code Example
from pyspark.sql import functions as F
events = spark.read.parquet(
"s3://analytics/raw/application-events/"
)
prepared = (
events
.filter(F.col("status").isNotNull())
.select(
"application_id",
"status",
"occurred_at"
)
.withColumn(
"event_date",
F.to_date("occurred_at")
)
)
daily_counts = (
prepared
.groupBy("event_date", "status")
.count()
)
# Inspect the optimized and physical plans.
daily_counts.explain(mode="formatted")
# The write action triggers execution.
daily_counts.write.mode("overwrite").parquet(
"s3://analytics/marts/daily-status-counts/"
)Common Interview Pitfalls
- Assuming every transformation executes immediately when it is declared.
- Calling collect on a dataset that cannot safely fit in driver memory.
- Using Python user-defined functions when built-in Spark SQL expressions are available.
- Running several actions without recognizing that upstream computation may repeat.
- Ignoring the physical plan when a structured query performs poorly.
- Using an RDD for ordinary structured transformations without a clear requirement.
- Reading every source column when only a small subset is required.
How do partitions and shuffles affect Spark joins and aggregations?
Direct Answer
Partitions determine parallel work placement, while shuffles redistribute records across executors for operations such as joins, grouping, sorting, and repartitioning.
Detailed Explanation
A Spark partition is a logical subset of data processed by one task at a time.
Partitioning affects:
A shuffle redistributes data so records with related keys can be processed together. Shuffles commonly occur during:
groupBy and aggregationsdistinctrepartitionShuffle operations involve network transfer, serialization, disk spill, sorting, and intermediate files. They are therefore more expensive and failure-prone than narrow transformations such as simple filtering or column projection.
A join between two large datasets commonly requires both sides to be partitioned by the join key. If one dataset is small enough, Spark may broadcast it to executors and avoid shuffling the larger side.
repartition performs a shuffle and can increase or decrease partition count. It is useful when data must be redistributed evenly or organized by keys.
coalesce is commonly used to reduce partitions with less redistribution, but a drastic coalesce can concentrate work onto very few executors.
Too few partitions reduce parallelism and create large tasks. Too many tiny partitions increase scheduling overhead and often create a small-files problem in the output.
Partitioning should be selected from data volume, executor resources, operation type, skew, and desired output layout rather than from a fixed number copied from another pipeline.
Code Example
from pyspark.sql import functions as F
applications = spark.read.parquet(
"s3://analytics/facts/applications/"
)
companies = spark.read.parquet(
"s3://analytics/dimensions/companies/"
)
# Repartition the large fact dataset for a key-based operation.
partitioned_applications = applications.repartition(
400,
"company_id"
)
# Broadcast only when the dimension is safely small.
enriched = partitioned_applications.join(
F.broadcast(companies),
on="company_id",
how="left"
)
summary = (
enriched
.groupBy("industry")
.agg(
F.countDistinct("application_id")
.alias("applications")
)
)
summary.explain(mode="formatted")Common Interview Pitfalls
- Treating every shuffle as a problem that must always be eliminated.
- Using one partition for a large output and forcing serial execution.
- Broadcasting a dataset that is too large for executor memory.
- Using repartition repeatedly without understanding each shuffle it creates.
- Producing thousands of tiny partitions and output files.
- Assuming source-file partitions remain optimal after joins and aggregations.
- Using groupByKey-style collection when an aggregation can reduce data earlier.
How should a data engineer diagnose and reduce data skew in Spark joins and aggregations?
Direct Answer
Inspect task and partition distributions, identify hot keys, filter unnecessary data, use suitable join strategies, salt keys when necessary, and allow AQE to optimize runtime plans.
Detailed Explanation
Data skew occurs when records are distributed unevenly across partitions. A small number of tasks then process much more data than the others.
Common symptoms include:
Skew commonly arises from:
A useful investigation includes:
1. Inspect the physical plan.
2. Review Spark UI task-duration and shuffle-size distributions.
3. Measure key frequencies.
4. Confirm whether filters and column pruning occur before the shuffle.
5. Verify join cardinality and output expansion.
Potential mitigations include:
Adaptive Query Execution uses runtime statistics to revise parts of a Spark SQL plan. It can coalesce post-shuffle partitions, convert join strategies, and optimize some skewed joins.
AQE reduces manual tuning but cannot correct an invalid join, an extreme business-key concentration, or a transformation that multiplies rows unexpectedly.
Code Example
from pyspark.sql import functions as F
key_distribution = (
applications
.groupBy("company_id")
.count()
.orderBy(F.desc("count"))
)
key_distribution.show(20, truncate=False)
# Example salting for a known hot key.
salt_buckets = 16
salted_applications = applications.withColumn(
"salt",
F.when(
F.col("company_id") == "hot-company",
(
F.rand(seed=42) * salt_buckets
).cast("integer")
).otherwise(F.lit(0))
)
salt_values = spark.range(
0,
salt_buckets
).withColumnRenamed("id", "salt")
salted_companies = (
companies
.filter(F.col("company_id") == "hot-company")
.crossJoin(salt_values)
.unionByName(
companies
.filter(F.col("company_id") != "hot-company")
.withColumn("salt", F.lit(0))
)
)
joined = salted_applications.join(
salted_companies,
on=["company_id", "salt"],
how="left"
)Common Interview Pitfalls
- Increasing executor memory without investigating the skewed key distribution.
- Adding random salting without preserving correct join or aggregation semantics.
- Broadcasting a relation based only on row count rather than serialized size.
- Assuming Adaptive Query Execution corrects every source of skew automatically.
- Ignoring null and default values that concentrate into one join partition.
- Repartitioning on the same severely skewed key without another mitigation.
- Treating long tasks as skew without comparing input and shuffle distributions.
- Using a join that unintentionally creates a many-to-many row explosion.
How do Spark lineage, caching, persistence, checkpointing, and task retries support fault tolerance?
Direct Answer
Spark can recompute lost partitions from lineage, persistence avoids repeated work, checkpoints truncate lineage using durable storage, and failed tasks can be retried.
Detailed Explanation
Spark transformations form a dependency graph describing how an output can be derived from its inputs. This history is commonly called lineage.
If an executor loses an intermediate partition, Spark can often recompute that partition from its lineage rather than requiring every intermediate result to be replicated permanently.
Caching or persistence materializes a dataset so later actions can reuse it instead of recomputing the full lineage.
Persistence is useful when:
Caching every DataFrame is not beneficial. It consumes executor memory, may spill to disk, can increase garbage collection, and can preserve data that is used only once.
Cached datasets should be released with unpersist when no longer required.
Checkpointing materializes data to reliable storage and truncates the lineage. It can be useful for very long dependency chains or stateful processing where recovery should not replay the complete history.
Local checkpointing uses executor-local storage and sacrifices reliable fault tolerance for performance. It should not be treated as equivalent to checkpointing in durable distributed storage.
Task retries help recover from transient executor or fetch failures, but repeated retry cannot correct deterministic code errors, corrupted source data, insufficient memory, or an unavailable destination.
End-to-end correctness also depends on the output sink. A retried task can duplicate external effects unless writes are idempotent or transactionally committed.
Code Example
from pyspark import StorageLevel
prepared = (
events
.filter("application_id IS NOT NULL")
.select(
"application_id",
"status",
"occurred_at"
)
.persist(StorageLevel.MEMORY_AND_DISK)
)
# First action materializes persisted partitions.
event_count = prepared.count()
# Subsequent actions can reuse them.
status_counts = (
prepared
.groupBy("status")
.count()
)
latest_events = (
prepared
.groupBy("application_id")
.max("occurred_at")
)
status_counts.write.mode("overwrite").parquet(
"s3://analytics/marts/status-counts/"
)
latest_events.write.mode("overwrite").parquet(
"s3://analytics/marts/latest-events/"
)
prepared.unpersist()Common Interview Pitfalls
- Caching every DataFrame regardless of reuse or memory cost.
- Forgetting to unpersist datasets after their final use.
- Assuming cached data survives every executor or cluster failure.
- Treating local checkpointing as durable fault-tolerant storage.
- Using retries for deterministic transformation or schema errors.
- Assuming Spark task retry makes an external side effect exactly once.
- Creating extremely long lineage without evaluating checkpointing or materialization.
How would you design and troubleshoot a reliable production Apache Spark pipeline processing several terabytes of daily data?
Direct Answer
Use partitioned durable inputs, explicit schemas, incremental idempotent processing, optimized joins, measured resources, quality gates, observability, and controlled publication.
Detailed Explanation
A production Spark pipeline should be designed around correctness, replayability, workload shape, data distribution, and destination behavior before cluster size is selected.
Input design
Incremental boundaries
Process explicit dates, partitions, manifests, or source positions. Make reruns deterministic and idempotent. Avoid deriving the processing interval from the current wall-clock time.
Transformation plan
Partition strategy
Input partitioning, shuffle partitioning, and output partitioning solve different problems.
The pipeline should maintain enough partitions for parallel execution without producing excessive task overhead or tiny output files. Adaptive Query Execution can improve runtime partition and join decisions, but it should be monitored rather than assumed to fix every workload.
Resources
Choose executor memory, cores, overhead, and parallelism from measured task behavior. Very large executors can create long garbage-collection pauses and increase the impact of one executor failure. Very small executors may add overhead and reduce efficiency.
Reliability
Data quality
Validate record counts, uniqueness, nullability, accepted values, reconciliation, freshness, and business metrics. Quarantine invalid records where partial processing is permitted.
Observability
Track:
Use the Spark UI and event logs to compare successful and degraded runs.
Troubleshooting sequence
1. Confirm the exact failing stage, input partition, and application version.
2. Inspect the physical plan.
3. Compare task duration and shuffle distributions.
4. Check executor memory, spill, garbage collection, and loss.
5. Measure key skew and join cardinality.
6. Verify source-file sizes and partition pruning.
7. Test one targeted change at a time.
The pipeline should publish data only after the expected partition set is complete and required quality checks pass.
Code Example
from pyspark.sql import functions as F
processing_date = dbutils.widgets.get(
"processing_date"
)
events = (
spark.read
.schema(application_event_schema)
.parquet(
"s3://analytics/raw/application-events/"
f"event_date={processing_date}/"
)
)
companies = (
spark.read
.schema(company_schema)
.parquet(
"s3://analytics/dimensions/companies/"
)
)
prepared = (
events
.filter(F.col("application_id").isNotNull())
.select(
"event_id",
"application_id",
"company_id",
"status",
"occurred_at"
)
.dropDuplicates(["event_id"])
)
enriched = prepared.join(
F.broadcast(companies),
on="company_id",
how="left"
)
output = (
enriched
.repartition(
200,
F.col("event_date")
)
)
staging_path = (
"s3://analytics/staging/"
f"application-events/{processing_date}/"
)
output.write.mode("overwrite").parquet(
staging_path
)
run_quality_checks(
dataset_path=staging_path,
processing_date=processing_date
)
publish_partition_atomically(
staging_path=staging_path,
target_path=(
"s3://analytics/facts/"
f"application-events/event_date={processing_date}/"
)
)Common Interview Pitfalls
- Starting performance tuning by increasing cluster size without inspecting the physical plan.
- Inferring the schema repeatedly across a large production dataset.
- Partitioning output by a high-cardinality identifier and creating tiny files.
- Broadcasting an input without measuring its serialized size.
- Allowing concurrent runs to overwrite the same output partition.
- Publishing output before completeness and quality checks pass.
- Using Python UDFs for transformations available through Spark SQL functions.
- Ignoring key skew and accidental many-to-many joins.
- Using one extremely large executor configuration for every stage and workload.
- Measuring job success without monitoring data freshness and reconciliation.
What is the difference between batch processing and stream processing, and when should each be used?
Direct Answer
Batch processing handles bounded collections on a schedule, while stream processing continuously handles unbounded events as they arrive with lower processing latency.
Detailed Explanation
Batch processing operates on a bounded collection of data.
Examples include:
A batch has a defined input boundary, such as a date partition, file manifest, identifier range, or extraction watermark.
Batch workloads commonly prioritize:
Stream processing operates on an unbounded sequence of events that continues to arrive.
Examples include:
Streaming workloads commonly prioritize:
The distinction is not simply whether a tool runs continuously. A streaming system may still group events into time windows, and a batch system may run every few minutes.
The correct choice depends on business latency requirements. If a metric is consumed once each morning, a reliable daily batch may be simpler and less expensive than a continuously running streaming platform.
Many platforms use both approaches. Streaming pipelines provide timely operational views, while batch pipelines perform reconciliation, historical correction, and complex analytical rebuilding.
The two paths must use compatible business definitions, or real-time dashboards and warehouse reports may disagree.
Code Example
type ProcessingRequirement = {
maximumLatencyMinutes: number;
requiresHistoricalReplay: boolean;
inputIsContinuous: boolean;
outputRequiresImmediateAction: boolean;
};
function chooseProcessingMode(
requirement: ProcessingRequirement
): 'batch' | 'streaming' | 'hybrid' {
if (
requirement.outputRequiresImmediateAction &&
requirement.inputIsContinuous
) {
return requirement.requiresHistoricalReplay
? 'hybrid'
: 'streaming';
}
if (
requirement.maximumLatencyMinutes >= 60
) {
return 'batch';
}
return 'hybrid';
}Common Interview Pitfalls
- Choosing streaming because it appears more modern without a low-latency requirement.
- Assuming stream processing eliminates the need for historical batch reconciliation.
- Running frequent micro-batches while claiming true per-event processing semantics.
- Building separate batch and streaming definitions that calculate different business metrics.
- Ignoring the operational cost of continuously running stateful streaming jobs.
- Using batch processing for decisions that require immediate user or security action.
What are events, topics, partitions, offsets, producers, consumers, and consumer groups in Apache Kafka?
Direct Answer
Producers publish events to partitioned topics, offsets identify records within each partition, and consumer groups divide partition ownership among cooperating consumers.
Detailed Explanation
Apache Kafka stores and distributes event records through several core concepts.
An event or record commonly contains:
A producer publishes records to a topic.
A topic is a named stream of related records, such as:
job-application-eventsresume-analysis-completedsubscription-eventsA topic is divided into partitions. Partitions provide parallelism and an ordered log within each individual partition.
Kafka does not provide one global order across every partition in a topic. Ordering is guaranteed only within a partition according to the records written there.
A producer may use a record key to select a partition. Events with the same key can therefore be routed to the same partition, supporting per-key ordering.
An offset is a record’s position within one partition. The same numeric offset can exist in several partitions, so a complete position includes the topic, partition, and offset.
A consumer reads records from topic partitions.
A consumer group represents one logical subscriber. Kafka distributes topic partitions among active consumers in the group. Within one consumer group, one partition is normally assigned to one consumer at a time.
This means useful parallelism is limited by partition count. If a topic has six partitions, adding a seventh consumer to the same group does not provide another active partition assignment for that topic.
Different consumer groups can read the same topic independently. For example, analytics, fraud detection, and notification services may each use a separate group.
Consumers commit offsets to record their progress. A committed offset supports recovery, but the timing of processing and committing affects whether records may be replayed or skipped after failure.
Code Example
import {
Kafka,
logLevel
} from 'kafkajs';
const kafka = new Kafka({
clientId: 'application-analytics',
brokers: ['broker-1:9092', 'broker-2:9092'],
logLevel: logLevel.INFO
});
const consumer = kafka.consumer({
groupId: 'application-warehouse-v1'
});
await consumer.connect();
await consumer.subscribe({
topic: 'job-application-events',
fromBeginning: false
});
await consumer.run({
eachMessage: async ({
topic,
partition,
message
}) => {
const eventId = message.key?.toString();
const payload = message.value?.toString();
await processEventIdempotently({
eventId,
payload,
topic,
partition,
offset: message.offset
});
}
});Common Interview Pitfalls
- Assuming records are globally ordered across every partition in a topic.
- Using random partitioning when per-entity event ordering is required.
- Adding more consumers than partitions and expecting additional parallelism.
- Treating an offset as globally unique without topic and partition information.
- Using the same consumer group for services that need independent copies of events.
- Committing offsets before required destination processing has completed.
- Choosing partition keys that create severe hot-partition skew.
How do event time, processing time, windows, watermarks, and allowed lateness affect streaming results?
Direct Answer
Event time reflects when an event occurred, processing time reflects when it was handled, windows group events, and watermarks estimate event-time progress.
Detailed Explanation
Event time is the time at which an event occurred in the source or business process.
Processing time is the time at which the streaming operator processes the event.
The two can differ because of:
Event-time processing produces results according to business occurrence time rather than machine arrival time.
A window groups an unbounded stream into finite analytical sets.
Common window types include:
A watermark represents the system’s estimate of event-time progress. When a watermark passes a window boundary, the system can trigger computation for that window.
Watermarks are not necessarily proof that no earlier event will ever arrive. They express an operational completeness assumption.
A late event has an event timestamp earlier than the current watermark or a completed window boundary.
Possible policies include:
Long allowed lateness improves completeness but increases retained state and delays finality. Aggressive watermarks reduce latency but can classify more records as late.
Watermark progress may stall when one source partition becomes idle or delayed. Production systems should monitor watermark lag and configure idle-source handling where supported.
Code Example
from datetime import timedelta
import apache_beam as beam
events = (
pipeline
| 'ReadEvents' >> read_application_events()
| 'AssignEventTimestamps' >> beam.Map(
attach_source_event_timestamp
)
)
windowed = (
events
| 'FiveMinuteWindows' >> beam.WindowInto(
beam.window.FixedWindows(5 * 60),
allowed_lateness=10 * 60,
accumulation_mode=(
beam.trigger.AccumulationMode.ACCUMULATING
)
)
| 'KeyByStatus' >> beam.Map(
lambda event: (event['status'], 1)
)
| 'CountByStatus' >> beam.CombinePerKey(sum)
)Common Interview Pitfalls
- Using processing time for a business metric that requires source event time.
- Assuming a watermark guarantees that no earlier event can arrive.
- Dropping late data without measuring or documenting the resulting incompleteness.
- Keeping windows open indefinitely and allowing unbounded state growth.
- Using one event timestamp field without defining its business meaning.
- Ignoring idle partitions that prevent watermark advancement.
- Comparing real-time and batch metrics without applying the same late-data policy.
- Using a sliding window without accounting for records appearing in several windows.
How do at-most-once, at-least-once, and exactly-once processing differ, and how should replay and deduplication be designed?
Direct Answer
At-most-once may lose records, at-least-once may duplicate them, and exactly-once requires coordinated processing, state, and destination semantics.
Detailed Explanation
Delivery and processing guarantees describe what can happen when failures occur.
At-most-once
A record is processed zero or one time. The system avoids duplicates but may lose a record when progress is committed before processing completes.
At-least-once
A record is processed one or more times. The system retries after uncertain failure, preventing silent loss but allowing duplicates.
Exactly-once
The intended effect of each logical input appears exactly once in the defined processing scope.
Exactly-once is not created merely by changing a consumer configuration. It may require coordination among:
A stream processor may provide exactly-once state updates inside its managed pipeline, while an external HTTP API can still receive duplicate requests. Guarantees must therefore be evaluated end to end.
Replay means reading retained events again from an earlier offset, timestamp, or durable archive.
Replay is useful for:
Replay-safe consumers should avoid uncontrolled side effects. A historical replay should not resend old customer notifications or charge a payment again.
Deduplication commonly relies on a stable event identifier and a bounded retention policy. Deduplicating only by payload can incorrectly merge separate legitimate events or fail when irrelevant metadata changes.
Idempotent sinks use deterministic keys, upserts, transactional commits, partition replacement, or compare-and-set logic so reprocessing the same event does not create an additional effect.
The required guarantee should match business consequences. Duplicate analytics ingestion may be manageable with deduplication, while duplicate financial side effects require stricter controls.
Code Example
BEGIN;
INSERT INTO analytics.application_events (
event_id,
application_id,
status,
occurred_at,
source_partition,
source_offset
)
VALUES (
:event_id,
:application_id,
:status,
:occurred_at,
:source_partition,
:source_offset
)
ON CONFLICT (event_id)
DO UPDATE SET
application_id = EXCLUDED.application_id,
status = EXCLUDED.status,
occurred_at = EXCLUDED.occurred_at,
source_partition = EXCLUDED.source_partition,
source_offset = EXCLUDED.source_offset;
INSERT INTO streaming_consumer_progress (
consumer_name,
source_partition,
committed_offset
)
VALUES (
:consumer_name,
:source_partition,
:next_offset
)
ON CONFLICT (
consumer_name,
source_partition
)
DO UPDATE SET
committed_offset = EXCLUDED.committed_offset;
COMMIT;Common Interview Pitfalls
- Claiming exactly-once behavior based only on the message broker configuration.
- Committing source progress before the destination effect succeeds.
- Using at-least-once processing without stable event identifiers.
- Replaying historical events through consumers that trigger irreversible side effects.
- Deduplicating events by complete payload rather than a logical event key.
- Keeping deduplication state forever without a bounded retention requirement.
- Assuming an external API participates in a stream-processing transaction.
- Selecting expensive exactly-once processing when duplicates are safely tolerated.
How should data quality, schema contracts, lineage, and governance be implemented across batch and streaming pipelines?
Direct Answer
Define enforceable schemas and quality rules, track dataset lineage and ownership, classify sensitive fields, measure freshness, and control publication and access.
Detailed Explanation
Reliable data platforms must define not only how data is processed but also what the data means, who owns it, and which guarantees consumers may depend on.
Data quality dimensions commonly include:
Quality checks should exist at several layers.
Ingestion checks
Transformation checks
Publication checks
A data contract defines the expectations between producers and consumers. It may specify:
Schema compatibility does not guarantee semantic compatibility. Changing status = "completed" from meaning “processing finished” to “user approved” is a breaking semantic change even when the data type remains text.
Lineage records how a dataset or field was derived from upstream sources and transformations. It helps with impact analysis, incident investigation, compliance, and deprecation.
Governance includes ownership, access control, classification, retention, auditability, documentation, and approved use.
Not every quality failure should have the same behavior. A missing critical identifier may block publication, while a noncritical optional attribute may be quarantined or reported without stopping the entire pipeline.
Code Example
version: 2
models:
- name: fact_application_events
description: >
One row per unique application status event.
config:
contract:
enforced: true
columns:
- name: event_id
data_type: string
constraints:
- type: not_null
- type: unique
- name: application_id
data_type: string
constraints:
- type: not_null
- name: status
data_type: string
constraints:
- type: not_null
data_tests:
- accepted_values:
arguments:
values:
- saved
- applied
- interview
- offer
- rejected
- name: occurred_at
data_type: timestamp
constraints:
- type: not_nullCommon Interview Pitfalls
- Testing only whether a pipeline task ran without validating the produced data.
- Treating schema compatibility as proof that business meaning is unchanged.
- Adding fields without documenting ownership, meaning, or sensitivity.
- Blocking every pipeline for minor noncritical quality deviations.
- Publishing incomplete partitions before freshness and reconciliation checks pass.
- Tracking table-level lineage while ignoring important field-level transformations.
- Giving broad analytical access to raw personal or document content.
- Changing or deleting contracted fields without a deprecation period.
- Defining quality thresholds without an owner or response process.
How would you design a reliable cloud streaming platform for product events, application activity, billing events, and real-time analytics?
Direct Answer
Use durable partitioned ingestion, governed schemas, event-time processing, replayable storage, idempotent sinks, autoscaling, quality controls, and observable recovery paths.
Detailed Explanation
A production streaming platform should preserve events durably, support independent consumers, tolerate failures and replay, and provide timely results without silently sacrificing correctness.
Event design
Define events around completed business facts rather than internal implementation details.
Each event should include:
Avoid placing credentials, full resume documents, or unnecessary personal data in general-purpose event streams.
Ingestion layer
Use a durable, partitioned event service such as Kafka or a managed cloud messaging service.
Select partition keys based on required ordering and traffic distribution. A user or application identifier may preserve entity-level order, but a small number of very active keys can create hot partitions.
Configure replication, retention, encryption, authentication, authorization, and quotas. Retention should support expected recovery and replay requirements.
Schema and contracts
Store versioned event schemas and define compatibility rules. Additive optional fields are generally easier to evolve than renaming, removing, or changing the meaning of an existing field.
Validate producers before incompatible events reach downstream consumers.
Processing layer
Use a managed or self-operated stream processor capable of:
Separate independent business workloads so one slow consumer does not block every use case.
Storage and serving
Write raw events to durable object storage in addition to operational stream retention when long-term replay is required.
Publish processed data into destinations suited to each use case:
Sinks must be idempotent or transactionally coordinated with processing state.
Quality and reconciliation
Track source and sink record counts, duplicates, invalid records, late events, schema failures, and event-time freshness.
Use periodic batch reconciliation to compare durable raw events with streaming outputs and repair missed or incorrectly processed data.
Reliability
Plan for:
Use bounded retries and quarantine repeatedly failing records. Unlimited retries can stop partition progress and create growing lag.
Observability
Monitor:
Deployment and recovery
Deploy processor changes progressively. Preserve compatible state or define a controlled migration and replay strategy.
Run recovery exercises that restart consumers, restore state, replay retained data, and validate downstream reconciliation.
A streaming pipeline is not complete merely because events move quickly. It must prove that events are durable, understandable, recoverable, and reflected correctly in downstream systems.
Code Example
type StreamingEvent<TPayload> = {
eventId: string;
eventType: string;
eventVersion: number;
partitionKey: string;
occurredAt: string;
producer: {
service: string;
version: string;
};
correlationId?: string;
payload: TPayload;
};
type ApplicationStatusChanged = {
applicationId: string;
userId: string;
previousStatus: string | null;
currentStatus: string;
};
const event: StreamingEvent<
ApplicationStatusChanged
> = {
eventId: crypto.randomUUID(),
eventType: 'application.status.changed',
eventVersion: 1,
partitionKey: 'application_123',
occurredAt: new Date().toISOString(),
producer: {
service: 'application-service',
version: '2026.08.05'
},
correlationId: 'request_456',
payload: {
applicationId: 'application_123',
userId: 'user_789',
previousStatus: 'applied',
currentStatus: 'interview'
}
};Common Interview Pitfalls
- Using an event service without retaining enough history for required replay.
- Selecting a partition key that sends most traffic to one partition.
- Publishing events without stable identifiers or schema versions.
- Treating broker delivery as proof that the downstream destination was updated.
- Allowing poison messages to retry forever and block partition progress.
- Using processing time when reports require business event time.
- Sending sensitive documents or secrets through broadly accessible streams.
- Deploying stateful processor changes without a state migration or replay plan.
- Monitoring message throughput without monitoring consumer lag and event freshness.
- Claiming exactly-once behavior without evaluating the final sink.
How do partitioning, clustering, indexing, and columnar file formats improve data-system performance?
Direct Answer
Partitioning skips irrelevant data ranges, clustering groups related values, indexes accelerate selected lookups, and columnar formats reduce analytical I/O.
Detailed Explanation
Physical data layout determines how much data a query must locate, read, transfer, decompress, and process.
Partitioning divides a table or dataset into separate segments based on a field such as date, integer range, region, or tenant.
For example, a table partitioned by event_date can avoid scanning years of data when a query requests only one week. This elimination is often called partition pruning.
A useful partition field should:
Partitioning by a very high-cardinality identifier such as user_id can create too many small partitions and expensive metadata operations.
Clustering organizes data within a table or partition using one or more commonly filtered or joined columns. The engine can use block metadata to skip ranges that cannot satisfy the query.
Indexes are auxiliary structures used primarily by databases to locate selected rows without scanning the entire relation. They are useful for selective filters, joins, and ordered access, but they increase storage and write-maintenance cost.
Columnar file formats, such as Parquet, store values by column rather than storing complete rows together. Analytical engines can read only required columns, apply compression efficiently, and sometimes use file statistics for predicate pushdown.
CSV and JSON are useful interchange formats, but they generally provide weaker typing, compression, pruning, and schema evolution for large analytical workloads.
These techniques solve different problems and can be combined. A data lake table may use date partitions, Parquet files, and clustering or sorting inside each partition, while an operational PostgreSQL table may use declarative partitioning and B-tree indexes.
Code Example
-- Warehouse-style table layout.
CREATE TABLE analytics.application_events (
event_id TEXT NOT NULL,
application_id TEXT NOT NULL,
company_id TEXT NOT NULL,
status TEXT NOT NULL,
occurred_at TIMESTAMPTZ NOT NULL,
event_date DATE NOT NULL
)
PARTITION BY RANGE (event_date);
CREATE TABLE analytics.application_events_2026_08
PARTITION OF analytics.application_events
FOR VALUES FROM ('2026-08-01')
TO ('2026-09-01');
CREATE INDEX application_events_company_date_idx
ON analytics.application_events_2026_08 (
company_id,
event_date
);
-- A filter on event_date allows partition pruning.
SELECT
company_id,
COUNT(*) AS applications
FROM analytics.application_events
WHERE event_date >= DATE '2026-08-01'
AND event_date < DATE '2026-09-01'
GROUP BY company_id;Common Interview Pitfalls
- Partitioning by a high-cardinality identifier and creating thousands of tiny partitions.
- Assuming partitioning automatically helps queries that do not filter the partition field.
- Creating indexes for every column without considering write and storage overhead.
- Using CSV for large analytical datasets when column pruning and compression are important.
- Creating very small Parquet files that increase scheduling and metadata overhead.
- Selecting clustering columns that are rarely used in filters or joins.
- Confusing database indexes with data-lake partition directories.
What do data freshness, processing latency, throughput, completeness, and reliability mean in a data platform?
Direct Answer
Freshness measures how current data is, latency measures processing delay, throughput measures volume, completeness measures expected coverage, and reliability measures sustained correctness.
Detailed Explanation
A successful task does not necessarily mean that a dataset is healthy. Data platforms need indicators that represent the usefulness and correctness of their outputs.
Data freshness measures how current a dataset is relative to its expected update schedule or source event time.
Examples include:
Processing latency measures the delay between defined points in the pipeline.
Possible definitions include:
The start and end points must be stated explicitly.
Throughput measures the volume processed per unit of time, such as records per second, files per hour, or terabytes per day.
High throughput is not useful when records are duplicated, incomplete, stale, or incorrectly transformed.
Completeness measures whether all expected data was received and published. This may involve:
Reliability describes whether the data product consistently satisfies its defined correctness, freshness, availability, and recovery expectations.
Useful reliability indicators include:
Operational metrics should be connected to data-product objectives. CPU utilization may explain a problem, but users usually care whether trusted data arrived correctly and on time.
Code Example
type DataReliabilityMetrics = {
sourceMaxEventTime: Date;
publishedAt: Date;
expectedRecordCount: number;
publishedRecordCount: number;
duplicateRecordCount: number;
rejectedRecordCount: number;
};
function calculateReliability(
metrics: DataReliabilityMetrics
) {
const freshnessSeconds =
(
metrics.publishedAt.getTime() -
metrics.sourceMaxEventTime.getTime()
) / 1000;
const completeness =
metrics.expectedRecordCount === 0
? 1
: metrics.publishedRecordCount /
metrics.expectedRecordCount;
const duplicateRate =
metrics.publishedRecordCount === 0
? 0
: metrics.duplicateRecordCount /
metrics.publishedRecordCount;
return {
freshnessSeconds,
completeness,
duplicateRate,
rejectedRecordCount:
metrics.rejectedRecordCount
};
}Common Interview Pitfalls
- Treating task completion as proof that the published data is complete.
- Using freshness and processing duration as though they mean the same thing.
- Reporting throughput without measuring duplicates or rejected records.
- Defining latency without identifying its start and end points.
- Monitoring only infrastructure health and not data-product health.
- Using one freshness target for datasets with different business requirements.
- Ignoring source delays when interpreting pipeline latency.
How should a data engineer analyze a query plan and improve query performance using pruning, pushdown, join optimization, and statistics?
Direct Answer
Inspect the physical plan and runtime metrics, reduce scanned data, preserve pruning and pushdown, validate estimates, improve joins, and tune physical design from evidence.
Detailed Explanation
Query tuning should begin with evidence from the optimizer plan and runtime execution rather than assumptions.
A query plan shows operations such as:
Important questions include:
Partition pruning excludes partitions that cannot satisfy a filter. Functions or expressions that obscure the partition field may prevent pruning in some engines.
Predicate pushdown moves filters closer to the source so fewer rows are loaded into later operators.
Column pruning reads only required columns. It is especially effective with columnar storage.
Join performance depends on:
A broadcast join can avoid redistributing a large input when the other relation is safely small. A many-to-many join can multiply rows regardless of cluster size.
Optimizers rely on statistics. Stale or incomplete statistics can produce incorrect cardinality estimates and poor join choices.
In PostgreSQL, EXPLAIN displays the planned execution strategy. EXPLAIN ANALYZE executes the statement and reports actual runtime behavior, so it must be used carefully with mutating statements.
A tuning process should change one meaningful factor at a time and compare runtime, scanned bytes, plan shape, resource use, and result correctness.
Code Example
EXPLAIN (
ANALYZE,
BUFFERS,
VERBOSE
)
SELECT
c.industry,
COUNT(*) AS applications
FROM analytics.fact_job_application AS a
JOIN analytics.dim_company AS c
ON c.company_key = a.company_key
WHERE a.submitted_date >= DATE '2026-08-01'
AND a.submitted_date < DATE '2026-09-01'
GROUP BY c.industry;
-- Review:
-- 1. Estimated versus actual rows.
-- 2. Scan type and partition pruning.
-- 3. Join algorithm.
-- 4. Sort or hash memory behavior.
-- 5. Buffer reads and execution time.Common Interview Pitfalls
- Tuning a query without inspecting its physical execution plan.
- Adding indexes without confirming the filter selectivity and workload.
- Wrapping partition fields in expressions that prevent effective pruning.
- Selecting every column from a columnar dataset when only a few are required.
- Ignoring stale statistics and inaccurate row-count estimates.
- Increasing compute resources without detecting an accidental many-to-many join.
- Using EXPLAIN ANALYZE on a production write statement without understanding that it executes.
- Optimizing runtime while failing to verify that query results remain correct.
How should data pipelines be monitored, retried, recovered, and reconciled after failures?
Direct Answer
Track data and execution health, classify failures, retry transient errors safely, preserve checkpoints, isolate bad data, reconcile outputs, and replay bounded intervals.
Detailed Explanation
Pipeline observability must cover both execution health and data health.
Execution signals include:
Data signals include:
Failures should be classified before retrying.
Transient failures may include temporary network errors, service throttling, or short destination outages. These can use bounded retries with delay and backoff.
Deterministic failures include invalid schemas, unsupported values, broken transformation logic, or missing required fields. Repeating the same operation without changing the input or code will not fix them.
Retries must be idempotent. The pipeline should use stable keys, merge behavior, atomic partition replacement, or transactional checkpoints so a repeated task does not duplicate data.
Recovery should preserve evidence such as:
Invalid records can be quarantined when business policy permits valid records to continue. The quarantine must retain enough context for correction and replay.
Reconciliation compares pipeline output against an authoritative expectation. Examples include:
Recovery should reprocess a bounded interval or partition rather than rebuilding unrelated data. Publication controls must prevent failed or partial recovery output from replacing trusted datasets.
Code Example
type PipelineRun = {
runId: string;
intervalStart: string;
intervalEnd: string;
codeVersion: string;
sourceRecords: number;
destinationRecords: number;
duplicateRecords: number;
rejectedRecords: number;
status:
| 'running'
| 'validated'
| 'published'
| 'failed';
};
function validateRun(run: PipelineRun): void {
const accountedFor =
run.destinationRecords +
run.rejectedRecords;
if (accountedFor !== run.sourceRecords) {
throw new Error(
'Source and destination counts do not reconcile'
);
}
if (run.duplicateRecords > 0) {
throw new Error(
'Duplicate records detected'
);
}
}
async function recoverRun(
failedRun: PipelineRun
): Promise<void> {
await replayInterval({
start: failedRun.intervalStart,
end: failedRun.intervalEnd,
codeVersion: failedRun.codeVersion,
idempotent: true
});
}Common Interview Pitfalls
- Monitoring task success without validating the resulting dataset.
- Retrying deterministic data or schema failures repeatedly.
- Restarting failed workers before preserving useful evidence.
- Running recovery writes without stable keys or partition isolation.
- Publishing partially recovered output before reconciliation passes.
- Quarantining invalid records without retaining their source context.
- Rebuilding an entire warehouse when only one bounded interval failed.
- Alerting on every retry rather than actionable user or data-product impact.
How should a data team optimize compute, storage, and query cost without weakening reliability or analytical usefulness?
Direct Answer
Measure cost by workload and data product, reduce unnecessary scans and recomputation, manage retention and file layout, isolate workloads, and optimize from usage evidence.
Detailed Explanation
Cost optimization should preserve correctness, reliability, security, and required performance.
A data platform commonly incurs cost from:
Useful cost attribution dimensions include:
Query and compute optimization
Storage optimization
Raw history may be intentionally retained for replay and audit. Cost reduction should not delete the only recoverable source without confirming recovery and compliance requirements.
Workload management
Separate interactive queries, scheduled transformations, streaming workloads, and large backfills through queues, reservations, pools, quotas, or dedicated compute.
One unrestricted backfill should not consume all warehouse capacity and delay production dashboards.
Autoscaling can reduce idle cost, but scaling policies must account for startup delay, state recovery, downstream quotas, and minimum availability.
Optimization should use unit economics such as:
The cheapest pipeline is not successful when its data is stale, incomplete, or unrecoverable.
Code Example
type DataWorkloadCost = {
workload: string;
computeCost: number;
storageCost: number;
networkCost: number;
successfulRuns: number;
processedTerabytes: number;
};
function calculateUnitCost(
cost: DataWorkloadCost
) {
const totalCost =
cost.computeCost +
cost.storageCost +
cost.networkCost;
return {
workload: cost.workload,
totalCost,
costPerSuccessfulRun:
cost.successfulRuns === 0
? null
: totalCost / cost.successfulRuns,
costPerTerabyte:
cost.processedTerabytes === 0
? null
: totalCost /
cost.processedTerabytes
};
}Common Interview Pitfalls
- Reducing retention without confirming replay, audit, or compliance requirements.
- Measuring total cloud spend without attributing cost to workloads or data products.
- Using full refreshes for large datasets when safe incremental processing is available.
- Allowing development queries and backfills to consume unrestricted production capacity.
- Creating many small compressed files that increase planning and read overhead.
- Deleting raw data while retaining only derived outputs that cannot be reproduced.
- Optimizing for minimum cost while violating freshness or reliability objectives.
- Scaling compute without controlling the volume of data scanned.
How would you design a reliable end-to-end data platform supporting batch ingestion, streaming, analytics, machine learning, governance, and historical replay?
Direct Answer
Use durable source-aligned ingestion, governed contracts, layered transformations, replayable processing, quality-gated publication, isolated workloads, observability, and tested recovery.
Detailed Explanation
An end-to-end data platform should make trusted data discoverable and timely while preserving correctness, security, lineage, reproducibility, and recoverability.
Source integration
Choose extraction according to source behavior:
Avoid creating an analytical dependency that degrades latency-sensitive production systems.
Durable ingestion
Land source-aligned data in durable storage before applying destructive business transformation.
Retain metadata such as:
This layer supports replay, auditing, incident investigation, and transformation correction.
Contracts and governance
Define ownership, schemas, field meaning, keys, compatibility, freshness, retention, sensitivity, and deprecation.
Apply least-privilege access, encryption, audit logging, masking, and separation of sensitive raw data from broadly accessible analytical models.
Processing architecture
Use batch processing for bounded transformations, reconciliation, historical rebuilding, and cost-efficient large-scale computation.
Use streaming when business decisions or operational views require low latency.
Both paths should share compatible metric and event definitions.
Layered modeling
Separate:
Every published model should have a defined grain and owner.
Reliability
Pipelines should be idempotent, interval-aware, restartable, and capable of bounded replay.
Use stable keys, manifests, checkpoints, atomic publication, versioned output, and concurrency controls.
Do not publish incomplete datasets merely because orchestration tasks completed.
Quality controls
Validate schema, uniqueness, nullability, accepted values, relationships, freshness, volume, reconciliation, and business metrics.
Classify checks as blocking, quarantining, or informational according to their impact.
Performance and cost
Use columnar formats, partitioning, clustering, indexing, compaction, incremental processing, workload queues, and materialized aggregates based on measured access patterns.
Maintain cost attribution and budgets by workload and data product.
Observability
Track:
Machine-learning support
Training and inference features must have stable definitions, event-time correctness, lineage, and point-in-time consistency to avoid leakage.
Store feature-generation code and source versions so datasets can be reproduced.
Recovery
Define recovery-time and recovery-point objectives for critical data products.
Test scenarios such as:
The platform is reliable only when restoration and replay procedures have been exercised successfully.
Operating model
Assign ownership for source contracts, pipelines, datasets, quality failures, access approvals, and incident response.
A strong platform provides standardized capabilities while allowing domain teams to own the meaning and quality of their data products.
Code Example
type DataProductDefinition = {
name: string;
owner: string;
grain: string;
sources: string[];
serviceLevel: {
maximumFreshnessMinutes: number;
minimumCompleteness: number;
};
qualityChecks: Array<{
name: string;
behavior:
| 'block-publication'
| 'quarantine'
| 'warn';
}>;
security: {
classification:
| 'public'
| 'internal'
| 'confidential'
| 'restricted';
retentionDays: number;
};
recovery: {
replayable: boolean;
recoveryTimeMinutes: number;
recoveryPointMinutes: number;
};
};
const applicationFunnel: DataProductDefinition = {
name: 'application-funnel',
owner: 'career-analytics',
grain: 'one row per job application',
sources: [
'application-status-events',
'jobs',
'companies'
],
serviceLevel: {
maximumFreshnessMinutes: 30,
minimumCompleteness: 0.999
},
qualityChecks: [
{
name: 'unique-application-id',
behavior: 'block-publication'
},
{
name: 'known-status-values',
behavior: 'quarantine'
}
],
security: {
classification: 'confidential',
retentionDays: 730
},
recovery: {
replayable: true,
recoveryTimeMinutes: 120,
recoveryPointMinutes: 15
}
};Common Interview Pitfalls
- Building the platform around tools instead of business requirements and data products.
- Transforming source data without retaining a durable replayable layer.
- Allowing batch and streaming systems to calculate the same metric differently.
- Publishing data without ownership, grain, freshness, or quality expectations.
- Giving broad users access to raw sensitive source content.
- Treating successful backups as proof that restoration will work.
- Allowing unrestricted backfills to compete with production workloads.
- Optimizing performance before establishing correctness and reconciliation.
- Supporting machine-learning features without point-in-time correctness.
- Centralizing technical infrastructure while leaving data meaning and quality unowned.
Want to tailer your resume for Data Engineer roles?
Import your resume, scan it for critical Data Engineer keywords, and compare it against ATS standards instantly.