Data engineering interviews test your ability to design reliable data pipelines, model data efficiently, and work across the full stack — SQL, Python, distributed systems, and cloud platforms. This guide covers the 50 most important questions with concise, accurate answers.
Quick reference
| Topic | Key concepts | Weight |
|---|---|---|
| ETL / ELT | pipelines, transformations, CDC | High |
| Data modeling | star/snowflake schema, SCD, vault | High |
| SQL | window functions, CTEs, optimization | High |
| Python | pandas, generators, testing | Medium |
| Spark / PySpark | RDDs, DataFrames, partitioning | High |
| Kafka | topics, partitions, consumer groups | Medium |
| Airflow | DAGs, operators, scheduling | High |
| Cloud platforms | BigQuery, Snowflake, Redshift | Medium |
| Data quality | testing, lineage, validation | Medium |
| Architecture | lambda, kappa, medallion | Medium |
Core data engineering concepts
1. What is data engineering?
Data engineering is the discipline of building and maintaining infrastructure and pipelines that collect, transform, and serve data for analysis and machine learning.
Key responsibilities:
- Design and build data pipelines (ETL/ELT)
- Model and warehouse data for analytical queries
- Ensure reliability, scalability, and data quality
- Collaborate with data scientists and analysts
2. What is the difference between ETL and ELT?
| Dimension | ETL | ELT |
|---|---|---|
| Order | Extract → Transform → Load | Extract → Load → Transform |
| Transform location | Staging server / middleware | Inside the data warehouse |
| Best for | Traditional DWH, sensitive masking | Cloud DWH (BigQuery, Snowflake, Redshift) |
| Flexibility | Schema must be defined upfront | Raw data preserved; schema-on-read |
| Tools | Informatica, Talend, SSIS | dbt, Spark, BigQuery SQL |
| Compute | ETL server handles load | Warehouse compute scales elastically |
Modern preference: ELT has become dominant because cloud warehouses have cheap, elastic compute — you load raw data first and transform inside the warehouse using SQL or dbt.
3. What is a data pipeline?
A data pipeline is an automated workflow that moves data from source to destination through a series of processing steps:
Source → Ingest → Validate → Transform → Load → Serve
Types:
- Batch pipeline — processes data on a schedule (hourly, daily)
- Streaming pipeline — processes data in real-time as events arrive
- Micro-batch — near-real-time, batches every few seconds (Spark Structured Streaming default)
4. What is the difference between a data warehouse, data lake, and data lakehouse?
| Dimension | Data Warehouse | Data Lake | Data Lakehouse |
|---|---|---|---|
| Storage | Structured, columnar | Raw (any format) | Raw + table format |
| Schema | Schema-on-write | Schema-on-read | Schema-on-write + read |
| Data types | Structured only | Any (text, images, logs) | Any |
| Query engine | SQL (optimised) | Spark/Hive (slow) | SQL + Spark |
| ACID | Yes | No (usually) | Yes (Delta/Iceberg/Hudi) |
| Examples | Snowflake, Redshift, BigQuery | S3, GCS, ADLS | Databricks, Apache Iceberg |
| Best for | BI / reporting | ML training data, archive | Unified analytics + ML |
Lakehouse (coined by Databricks) combines the flexibility of a lake with the reliability and query performance of a warehouse — using open table formats like Delta Lake, Apache Iceberg, or Apache Hudi.
5. What is Change Data Capture (CDC)?
CDC is a technique that identifies and captures row-level changes (INSERT, UPDATE, DELETE) in a source database and propagates them downstream.
Methods:
- Log-based CDC — reads database transaction log (binlog in MySQL, WAL in PostgreSQL). Most reliable, low overhead.
- Query-based CDC — polls tables using a
updated_attimestamp. Simple but misses deletes. - Trigger-based CDC — database triggers write changes to a shadow table.
Tools: Debezium (open-source, Kafka-based), AWS DMS, Fivetran, Airbyte.
MySQL binlog → Debezium connector → Kafka topic → Spark consumer → Warehouse
6. What is the difference between batch and streaming data processing?
| Dimension | Batch | Streaming |
|---|---|---|
| Trigger | Schedule (cron, Airflow) | Event-driven (continuous) |
| Latency | Minutes to hours | Milliseconds to seconds |
| Throughput | Very high | Medium |
| Complexity | Lower | Higher |
| Typical use | Reports, nightly ETL, ML training | Fraud detection, real-time dashboards |
| Tools | Spark, dbt, SQL | Kafka Streams, Flink, Spark Structured Streaming |
| Data volume | Large bounded sets | Unbounded event streams |
Lambda architecture combines both: a batch layer for accuracy and a speed layer for low latency.
Data modeling
7. What is a star schema?
A star schema organises data into a central fact table surrounded by dimension tables:
DimDate
|
DimProduct — FactSales — DimCustomer
|
DimStore
- Fact table — measurable business events (sales, clicks, orders). Contains foreign keys and numeric measures (revenue, quantity).
- Dimension table — descriptive attributes (product name, customer city, date).
Benefits: Simple joins, fast aggregations, easy for analysts to understand.
8. What is a snowflake schema?
A snowflake schema normalises dimension tables into sub-dimensions:
DimProduct → DimCategory → DimDepartment
| Dimension | Star Schema | Snowflake Schema |
|---|---|---|
| Redundancy | Denormalised (duplicate values) | Normalised (no duplicates) |
| Storage | More | Less |
| Query speed | Faster (fewer joins) | Slower (more joins) |
| Complexity | Simple | Complex |
| Maintenance | Harder to update | Easier to update |
Typical choice: Star schema for OLAP/reporting; snowflake if storage matters or dimensions are very large.
9. What are Slowly Changing Dimensions (SCD)?
SCDs handle how dimension records change over time.
| Type | Strategy | Use case |
|---|---|---|
| SCD Type 0 | Retain original, never change | Static reference data |
| SCD Type 1 | Overwrite — no history | Current state only (no audit needed) |
| SCD Type 2 | Add new row with valid_from/valid_to |
Full history (most common) |
| SCD Type 3 | Add column previous_value |
Only one previous state needed |
| SCD Type 4 | History table | Frequently changing attributes |
| SCD Type 6 | Hybrid Type 1+2+3 | Complex historical reporting |
Type 2 example:
-- New row added when customer city changes
customer_id | city | valid_from | valid_to | is_current
1001 | New York | 2023-01-01 | 2024-06-30 | false
1001 | Boston | 2024-07-01 | 9999-12-31 | true
10. What is a surrogate key vs a natural key?
| Natural Key | Surrogate Key | |
|---|---|---|
| Source | Business identifier (email, SSN, order_no) | System-generated integer or UUID |
| Uniqueness | May not be globally unique | Always unique |
| Stability | Can change (email updated) | Never changes |
| SCD Type 2 | Problematic (key changes) | Handles SCDs cleanly |
| Joins | Meaningful | Opaque |
Best practice: Use surrogate keys in the warehouse; keep natural key as business_key column.
11. What is data vault modeling?
Data Vault is a modeling methodology designed for auditability, scalability, and flexibility:
| Component | Contains | Example |
|---|---|---|
| Hub | Business keys | hub_customer(customer_bk, load_dt, source) |
| Link | Relationships between hubs | link_order_customer(order_bk, customer_bk) |
| Satellite | Descriptive attributes + history | sat_customer_details(customer_bk, name, city, valid_from) |
Pros: Audit trail, parallel loading, no DELETE/UPDATE (append-only).
Cons: Many tables, complex queries — usually materialised into a star schema for reporting (business vault / information mart).
SQL
12. Write a query to find the top 3 products by revenue per region.
WITH ranked AS (
SELECT
region,
product_id,
SUM(revenue) AS total_revenue,
RANK() OVER (PARTITION BY region ORDER BY SUM(revenue) DESC) AS rnk
FROM sales
GROUP BY region, product_id
)
SELECT region, product_id, total_revenue
FROM ranked
WHERE rnk <= 3;
13. What is the difference between RANK(), DENSE_RANK(), and ROW_NUMBER()?
| Function | Ties | Gaps |
|---|---|---|
ROW_NUMBER() |
Each row gets unique number | No gaps |
RANK() |
Same rank for ties | Gaps after ties (1,1,3) |
DENSE_RANK() |
Same rank for ties | No gaps (1,1,2) |
SELECT
name, score,
ROW_NUMBER() OVER (ORDER BY score DESC) AS row_num,
RANK() OVER (ORDER BY score DESC) AS rank,
DENSE_RANK() OVER (ORDER BY score DESC) AS dense_rank
FROM results;
-- score 90,90,80 → row_num 1,2,3 | rank 1,1,3 | dense_rank 1,1,2
14. What is a CTE and when would you use a recursive CTE?
A Common Table Expression (CTE) creates a named temporary result set within a query:
WITH monthly_revenue AS (
SELECT DATE_TRUNC('month', order_date) AS month, SUM(amount) AS revenue
FROM orders GROUP BY 1
),
growth AS (
SELECT month, revenue,
LAG(revenue) OVER (ORDER BY month) AS prev_revenue
FROM monthly_revenue
)
SELECT month, revenue,
ROUND((revenue - prev_revenue) / prev_revenue * 100, 2) AS growth_pct
FROM growth;
Recursive CTE for hierarchical data (org chart, category tree):
WITH RECURSIVE org_tree AS (
-- Anchor: top-level employees
SELECT id, name, manager_id, 0 AS depth
FROM employees WHERE manager_id IS NULL
UNION ALL
-- Recursive: add direct reports
SELECT e.id, e.name, e.manager_id, o.depth + 1
FROM employees e
JOIN org_tree o ON e.manager_id = o.id
)
SELECT * FROM org_tree ORDER BY depth, name;
15. How do you identify and remove duplicate rows?
-- Identify duplicates
SELECT email, COUNT(*) FROM users GROUP BY email HAVING COUNT(*) > 1;
-- Delete duplicates, keep latest row (PostgreSQL)
DELETE FROM users
WHERE id NOT IN (
SELECT MAX(id)
FROM users
GROUP BY email
);
-- Using ROW_NUMBER (works on all major databases)
WITH dupes AS (
SELECT id,
ROW_NUMBER() OVER (PARTITION BY email ORDER BY created_at DESC) AS rn
FROM users
)
DELETE FROM users WHERE id IN (SELECT id FROM dupes WHERE rn > 1);
16. What is query partitioning and how does it improve performance?
Partition pruning lets the query engine skip irrelevant partitions entirely:
-- Without partition: scans all 365 days of data
SELECT * FROM events WHERE event_date = '2024-01-15';
-- With partition on event_date: scans only 1/365 of data
CREATE TABLE events PARTITION BY RANGE (event_date);
CREATE TABLE events_2024_01 PARTITION OF events
FOR VALUES FROM ('2024-01-01') TO ('2024-02-01');
In BigQuery:
-- Only scans the partition for 2024-01-15 (~1 day of data)
SELECT * FROM `project.dataset.events`
WHERE DATE(_PARTITIONTIME) = '2024-01-15';
Python for data engineering
17. How do you read and process a large CSV file without loading it into memory?
import csv
def process_large_csv(filepath: str):
with open(filepath, 'r') as f:
reader = csv.DictReader(f)
for row in reader: # reads one row at a time
yield row # generator — memory-efficient
# Using pandas chunking
import pandas as pd
for chunk in pd.read_csv('large.csv', chunksize=100_000):
process(chunk) # each chunk is a DataFrame of 100k rows
18. What is the difference between map(), filter(), and list comprehensions in Python?
data = [1, 2, 3, 4, 5, 6]
# filter even numbers, square them
squares = [x**2 for x in data if x % 2 == 0] # list comprehension — preferred
squares = list(map(lambda x: x**2, filter(lambda x: x % 2 == 0, data))) # functional
# Generator expression (memory-efficient for large data)
total = sum(x**2 for x in data if x % 2 == 0)
For data engineering: prefer generator expressions and pandas vectorised operations over loops.
19. How do you handle missing values in pandas?
import pandas as pd
df = pd.read_csv('data.csv')
# Detect
print(df.isnull().sum())
# Drop rows where any column is null
df_clean = df.dropna()
# Drop only if ALL columns are null
df_clean = df.dropna(how='all')
# Fill with static value
df['age'] = df['age'].fillna(df['age'].median())
# Forward fill (time series)
df['price'] = df['price'].ffill()
# Flag instead of impute (preserve signal)
df['age_missing'] = df['age'].isnull().astype(int)
df['age'] = df['age'].fillna(-1)
20. How do you write unit tests for a data transformation function?
import pandas as pd
import pytest
def calculate_revenue(df: pd.DataFrame) -> pd.DataFrame:
"""Add revenue column: quantity * unit_price."""
df = df.copy() # immutable pattern
df['revenue'] = df['quantity'] * df['unit_price']
return df
# Test
def test_calculate_revenue():
input_df = pd.DataFrame({
'quantity': [2, 3],
'unit_price': [10.0, 5.0]
})
result = calculate_revenue(input_df)
assert list(result['revenue']) == [20.0, 15.0]
def test_calculate_revenue_zero_quantity():
input_df = pd.DataFrame({'quantity': [0], 'unit_price': [10.0]})
result = calculate_revenue(input_df)
assert result['revenue'].iloc[0] == 0.0
def test_calculate_revenue_does_not_mutate_input():
input_df = pd.DataFrame({'quantity': [1], 'unit_price': [5.0]})
_ = calculate_revenue(input_df)
assert 'revenue' not in input_df.columns # input unchanged
Apache Spark / PySpark
21. What is the difference between a transformation and an action in Spark?
| Transformation | Action | |
|---|---|---|
| Execution | Lazy — builds DAG | Triggers computation |
| Returns | New RDD/DataFrame | Value or writes to storage |
| Examples | filter, map, join, groupBy |
count, show, collect, write |
# Transformations (lazy — nothing runs yet)
df_filtered = df.filter(df.age > 25)
df_grouped = df_filtered.groupBy('country').agg(count('*'))
# Action — triggers the full DAG
df_grouped.show() # NOW Spark runs everything
22. What is the difference between repartition() and coalesce()?
repartition(n) |
coalesce(n) |
|
|---|---|---|
| Shuffle | Full shuffle | Avoids shuffle when reducing |
| Direction | Increase or decrease | Usually only decrease |
| Data distribution | Even (round-robin) | May create skewed partitions |
| Cost | High (full shuffle) | Low (partial shuffle) |
| Use case | Increasing parallelism | Reducing partitions before write |
# Writing to storage: coalesce to avoid many small files
df.coalesce(10).write.parquet('output/')
# Before a large join: repartition on join key to co-locate data
df.repartition(200, 'customer_id').join(other, 'customer_id')
23. What causes data skew in Spark and how do you fix it?
Skew occurs when some partitions are much larger than others (e.g., one customer has 80% of orders).
Detection:
df.groupBy(spark_partition_id()).count().show() # one partition >> others
Fixes:
| Fix | When to use |
|---|---|
| Salt the join key | Skewed join key (NULL or high-frequency value) |
| Broadcast join | Small dimension table (< 10MB default) |
| AQE (Spark 3+) | Enable spark.sql.adaptive.enabled=true |
| Repartition on multiple columns | Even distribution without salt |
# Salting: spread hot key across N partitions
from pyspark.sql.functions import col, concat_ws, lit, monotonically_increasing_id
N = 20
# Add random salt to big table
big = df_big.withColumn('salt', (monotonically_increasing_id() % N).cast('string'))
big = big.withColumn('salted_key', concat_ws('_', col('customer_id'), col('salt')))
# Explode small table N times
from pyspark.sql.functions import array, explode
small = df_small.withColumn('salt_array', array([lit(str(i)) for i in range(N)]))
small = small.withColumn('salt', explode('salt_array'))
small = small.withColumn('salted_key', concat_ws('_', col('customer_id'), col('salt')))
result = big.join(small, 'salted_key')
24. What file formats are used in data engineering and when?
| Format | Type | Compression | Schema | Best for |
|---|---|---|---|---|
| CSV | Row | None (gzip opt.) | No | Simple exchange, small data |
| JSON | Row | None (gzip opt.) | No | APIs, semi-structured |
| Parquet | Columnar | Snappy/Zstd | Yes | Analytics, Spark, BigQuery |
| ORC | Columnar | Zlib/Snappy | Yes | Hive, Hudi |
| Avro | Row | Snappy/Deflate | Yes | Kafka serialisation, schema evolution |
| Delta Lake | Columnar (Parquet) | Snappy | Yes + ACID | Lakehouse, upserts |
| Apache Iceberg | Columnar (Parquet) | Snappy | Yes + ACID | Multi-engine lakehouse |
Rule of thumb: Use Parquet for static analytics data; use Delta Lake / Iceberg when you need ACID transactions, schema evolution, or time travel.
25. What is a broadcast join in Spark?
A broadcast join sends a small table to every executor, avoiding a shuffle of the large table:
from pyspark.sql.functions import broadcast
# Manual broadcast hint
result = orders.join(broadcast(dim_country), 'country_code')
# Auto-broadcast if table < spark.sql.autoBroadcastJoinThreshold (default 10MB)
spark.conf.set('spark.sql.autoBroadcastJoinThreshold', '50m')
When to use: dimension table < 50–200 MB (depending on executor memory).
Apache Kafka
26. What is a Kafka topic, partition, and consumer group?
Topic: "orders"
├── Partition 0: [msg1, msg2, msg5] → Consumer A (Group 1)
├── Partition 1: [msg3, msg6] → Consumer B (Group 1)
└── Partition 2: [msg4, msg7] → Consumer C (Group 1)
- Topic — logical stream of messages (like a table)
- Partition — ordered, immutable log; unit of parallelism
- Consumer group — set of consumers that collectively read a topic; each partition assigned to one consumer
- Offset — message position within a partition; consumers commit offsets to track progress
27. How do you ensure exactly-once delivery in Kafka?
| Guarantee | Risk | Config |
|---|---|---|
| At-most-once | Data loss | acks=0 or no retry |
| At-least-once | Duplicates | acks=all, retry enabled |
| Exactly-once | Neither | Idempotent producer + transactions |
from confluent_kafka import Producer
producer = Producer({
'bootstrap.servers': 'broker:9092',
'enable.idempotence': True, # exactly-once at producer level
'acks': 'all',
'max.in.flight.requests.per.connection': 5,
'retries': 2147483647,
'transactional.id': 'my-transactional-id', # for cross-partition transactions
})
producer.init_transactions()
producer.begin_transaction()
try:
producer.produce('orders', key='k1', value='v1')
producer.commit_transaction()
except Exception:
producer.abort_transaction()
28. What is Kafka lag and how do you monitor it?
Consumer lag = latest offset in partition − consumer's committed offset.
High lag → consumer is falling behind producers.
# Check lag with kafka-consumer-groups CLI
kafka-consumer-groups.sh \
--bootstrap-server broker:9092 \
--describe --group my-group
# Shows: TOPIC | PARTITION | CURRENT-OFFSET | LOG-END-OFFSET | LAG
Monitor with: Kafka JMX metrics, Prometheus + kafka_exporter, Confluent Control Center, Burrow (LinkedIn's lag monitor).
Apache Airflow
29. What is an Airflow DAG?
A DAG (Directed Acyclic Graph) defines a workflow: tasks and their dependencies.
from airflow import DAG
from airflow.operators.python import PythonOperator
from airflow.operators.bash import BashOperator
from datetime import datetime, timedelta
default_args = {
'owner': 'data-team',
'retries': 2,
'retry_delay': timedelta(minutes=5),
'email_on_failure': True,
}
with DAG(
dag_id='daily_etl',
default_args=default_args,
start_date=datetime(2024, 1, 1),
schedule='@daily',
catchup=False,
tags=['etl', 'production'],
) as dag:
extract = BashOperator(task_id='extract', bash_command='python extract.py')
transform = PythonOperator(task_id='transform', python_callable=transform_fn)
load = PythonOperator(task_id='load', python_callable=load_fn)
extract >> transform >> load # dependency chain
30. What is the difference between Airflow sensors and operators?
| Operator | Sensor | |
|---|---|---|
| Purpose | Execute a task | Wait for a condition |
| Blocks slot | Only during execution | Yes (until condition met or timeout) |
| Variants | BashOperator, PythonOperator, etc. | S3KeySensor, ExternalTaskSensor, etc. |
| Mode | — | poke (runs in worker slot) or reschedule (frees slot between checks) |
from airflow.sensors.s3_key_sensor import S3KeySensor
wait_for_file = S3KeySensor(
task_id='wait_for_s3_file',
bucket_name='my-bucket',
bucket_key='data/{{ ds }}/input.parquet',
poke_interval=300, # check every 5 minutes
timeout=7200, # fail after 2 hours
mode='reschedule', # free worker slot between checks
)
31. What is XCom in Airflow?
XCom (cross-communication) lets tasks share small values between each other:
def extract(**context):
row_count = 42_000
context['ti'].xcom_push(key='row_count', value=row_count)
def validate(**context):
row_count = context['ti'].xcom_pull(task_ids='extract', key='row_count')
assert row_count > 0, 'No rows extracted!'
extract_task = PythonOperator(task_id='extract', python_callable=extract)
validate_task = PythonOperator(task_id='validate', python_callable=validate)
Warning: XCom is stored in Airflow's metadata database — keep values small (< 1MB). For large data, pass paths/URIs instead.
32. How do you handle Airflow DAG backfilling and catchup?
# catchup=True: backfill all runs since start_date (default)
# catchup=False: only run for current/future intervals
with DAG('my_dag', start_date=datetime(2024, 1, 1), catchup=False):
...
# Manual backfill via CLI
airflow dags backfill --start-date 2024-01-01 --end-date 2024-01-31 my_dag
Best practice: Set catchup=False for new DAGs unless you explicitly need historical runs. Use max_active_runs=1 to prevent overlapping runs.
dbt (data build tool)
33. What is dbt and how does it fit in the data stack?
dbt transforms raw data inside the warehouse using SQL + version control + testing:
Raw layer (S3/SFTP)
↓ Fivetran/Airbyte
Staging layer (raw warehouse tables)
↓ dbt models (SQL SELECT statements)
Intermediate layer (cleaned, joined)
↓
Marts layer (star schema, served to BI)
34. What are dbt models, sources, and seeds?
| Concept | Description | Example |
|---|---|---|
| Model | SQL SELECT file that defines a table/view | models/marts/dim_customer.sql |
| Source | Reference to raw upstream table | sources.yml → raw.public.customers |
| Seed | Static CSV loaded into warehouse | seeds/country_codes.csv |
| Snapshot | SCD Type 2 history tracking | snapshots/customer_snapshot.sql |
| Test | Data quality assertion | not_null, unique, accepted_values |
-- models/marts/dim_customer.sql
WITH source AS (
SELECT * FROM {{ source('raw', 'customers') }}
),
cleaned AS (
SELECT
customer_id,
TRIM(LOWER(email)) AS email,
COALESCE(country, 'Unknown') AS country,
created_at::DATE AS created_date
FROM source
WHERE customer_id IS NOT NULL
)
SELECT * FROM cleaned
Data quality
35. How do you implement data quality checks?
Layers of quality checks:
| Layer | What | Tools |
|---|---|---|
| Schema | Column names, types, nullability | dbt not_null, Great Expectations |
| Freshness | Data arrived on time | dbt source freshness, Airflow sensor |
| Volume | Row count within expected range | Custom dbt test, Monte Carlo |
| Distribution | No sudden spikes/drops in values | Monte Carlo, Anomalo |
| Referential | FK consistency across tables | dbt relationships test |
| Business | Domain-specific rules | dbt custom tests, Great Expectations |
# dbt schema.yml — built-in tests
models:
- name: dim_customer
columns:
- name: customer_id
tests:
- not_null
- unique
- name: email
tests:
- not_null
- unique
- name: country
tests:
- accepted_values:
values: ['US', 'UK', 'CA', 'DE']
36. What is data lineage?
Data lineage tracks the origin, movement, and transformation of data through pipelines:
CRM DB → Fivetran → raw.customers → dbt dim_customer → BI dashboard
Benefits: impact analysis (if raw table changes), debugging (where did this NULL come from?), compliance (GDPR data deletion).
Tools: dbt docs/lineage graph, OpenLineage, Apache Atlas, DataHub, Marquez.
Cloud data platforms
37. What is Snowflake's virtual warehouse and how does it scale?
A Snowflake virtual warehouse is a cluster of compute nodes independent of storage:
- Multi-cluster warehouses — automatically add clusters during peak load
- Auto-suspend — pauses after idle period (stops billing)
- Auto-resume — wakes on next query
-- Create a warehouse
CREATE WAREHOUSE etl_wh
WAREHOUSE_SIZE = 'LARGE'
AUTO_SUSPEND = 300 -- suspend after 5 min idle
AUTO_RESUME = TRUE
MIN_CLUSTER_COUNT = 1
MAX_CLUSTER_COUNT = 3; -- scale to 3 clusters under load
-- Use it
USE WAREHOUSE etl_wh;
38. What is BigQuery's partitioning and clustering?
Partitioning divides table data by a date/integer column — queries on that column scan only relevant partitions.
Clustering sorts data within each partition on up to 4 columns — reduces data scanned for filter/aggregate queries.
CREATE TABLE `project.dataset.events`
PARTITION BY DATE(event_ts) -- partition by day
CLUSTER BY user_id, event_type -- cluster within each partition
AS SELECT * FROM staging.events;
-- This query scans only 2024-01 partition and skips irrelevant user blocks
SELECT COUNT(*) FROM `project.dataset.events`
WHERE DATE(event_ts) = '2024-01-15'
AND user_id = '12345';
39. What is the difference between Redshift SORT KEY and DIST KEY?
| Key | Purpose | How it works |
|---|---|---|
| SORT KEY | Reduces data scanned | Data stored sorted; zone maps skip blocks |
| DIST KEY | Co-locates join data | Rows with same dist key on same node |
| DIST STYLE ALL | Replicate small dim table | Full copy on every node |
| DIST STYLE EVEN | Uniform distribution | Round-robin; good for large tables with no join |
CREATE TABLE fact_orders (
order_id BIGINT,
customer_id BIGINT,
order_date DATE,
revenue DECIMAL(10,2)
)
DISTKEY(customer_id) -- join partner dim_customer also on customer_id
SORTKEY(order_date); -- most queries filter by date
Architecture patterns
40. What is the Lambda architecture?
Lambda architecture processes data in two paths:
Source → Batch layer (accurate, hours-old) ─┐
→ Speed layer (approximate, real-time) ─┼→ Serving layer → BI
| Layer | Latency | Accuracy | Tools |
|---|---|---|---|
| Batch | Hours | High | Spark, Hive |
| Speed | Seconds | Approximate | Kafka Streams, Flink |
| Serving | Query time | Merged | Cassandra, Druid |
Limitation: Duplicated code (batch + streaming logic). Led to Kappa architecture.
41. What is the Kappa architecture?
Kappa removes the batch layer — process everything as a stream, reprocess historical data by replaying the event log:
Source → Event log (Kafka) → Stream processor (Flink/Spark) → Serving
↑ replay for reprocessing
Tradeoff: Simpler code but requires a long-retention event log and stream processor capable of high-throughput historical replay.
42. What is the Medallion architecture (Bronze/Silver/Gold)?
Used in Databricks/Delta Lake:
| Layer | Content | Example |
|---|---|---|
| Bronze (raw) | Raw data as-is, immutable | raw.events_2024_01 |
| Silver (cleaned) | Validated, deduplicated, typed | silver.events |
| Gold (aggregated) | Business-ready aggregations | gold.daily_revenue_by_region |
Benefits: data lineage tracing, easy rollback, incremental processing at each layer.
Performance and optimisation
43. How do you optimise a slow Spark job?
Checklist:
| Issue | Symptoms | Fix |
|---|---|---|
| Data skew | One task takes 10× longer | Salt join key, AQE |
| Too many small files | Slow planning, many tasks | coalesce() before write |
| Missing partition pruning | Full table scan | Filter on partition column |
| Large shuffle | Many GBs in shuffle stage | Reduce spark.sql.shuffle.partitions |
| Broadcast not triggered | Slow join | spark.sql.autoBroadcastJoinThreshold |
| UDFs | Slow Python serialisation | Replace with built-in Spark functions |
| GC pressure | OOM or slow GC | Increase executor memory, use Kyro serialiser |
44. What is incremental loading and why does it matter?
Incremental loading only processes new/changed records since the last run, rather than reloading everything (full refresh).
# Incremental in dbt
{{ config(materialized='incremental', unique_key='event_id') }}
SELECT event_id, user_id, event_type, event_ts
FROM {{ source('raw', 'events') }}
{% if is_incremental() %}
WHERE event_ts > (SELECT MAX(event_ts) FROM {{ this }})
{% endif %}
Benefits: faster runs, less cost, lower DWH load.
Risk: misses late-arriving data unless you look back N days.
Practical scenarios
45. Design a pipeline to ingest 10 million rows/day from a REST API.
Architecture:
REST API → Python ingestion worker → Kafka topic (raw_api_events)
→ Spark Structured Streaming → Bronze Delta table
→ dbt Silver model (schedule: hourly)
→ dbt Gold model (schedule: daily)
→ BI (Tableau/Looker)
Key decisions:
1. Pagination: cursor-based (not offset) for large datasets
2. Rate limiting: exponential backoff, token bucket
3. Idempotency: de-duplicate on event_id in Silver
4. Monitoring: row count alert, API error rate alert
5. Schema evolution: Avro + Schema Registry for Kafka
46. How would you handle late-arriving data in a streaming pipeline?
# Spark Structured Streaming with watermark
from pyspark.sql.functions import window, col
stream_df = (
spark.readStream.format('kafka')
.load()
.withWatermark('event_ts', '2 hours') # accept late data up to 2 hours
.groupBy(window(col('event_ts'), '1 hour'), col('country'))
.count()
)
Watermark tells Spark to drop state older than X hours — balances completeness vs memory.
For batch: use a lookback window — daily job reprocesses last 3 days to catch late data.
47. How do you perform a zero-downtime migration of a large warehouse table?
Expand-contract pattern:
Phase 1 — Expand:
Add new column(s) alongside old; write to both
Phase 2 — Migrate:
Backfill new column for existing rows
Phase 3 — Switch:
Update readers to use new column; update writers
Phase 4 — Contract:
Drop old column once all readers migrated
For table rename / schema change in Delta Lake / Iceberg:
-- Iceberg supports safe schema evolution
ALTER TABLE orders ADD COLUMN discount_pct DOUBLE;
ALTER TABLE orders RENAME COLUMN old_name TO new_name;
-- No data rewrite required
48. How do you monitor a production data pipeline?
| Signal | What to monitor | Alert threshold |
|---|---|---|
| Freshness | Time since last successful run | > 2× expected cadence |
| Row volume | Rows processed vs yesterday | ±30% anomaly |
| Error rate | Failed tasks / total tasks | > 1% |
| Pipeline duration | Job runtime | > 2× P90 baseline |
| Data quality | % null in key columns, FK violations | > 0.1% |
| Lag | Kafka consumer lag | > 100k messages |
Tools: Airflow alerts, Datadog, PagerDuty, Monte Carlo, Soda Core.
Advanced topics
49. What is the difference between OLTP and OLAP databases?
| Dimension | OLTP | OLAP |
|---|---|---|
| Purpose | Transactional writes | Analytical reads |
| Query type | Single row lookup / update | Multi-table aggregation |
| Schema | 3NF normalised | Star/snowflake denormalised |
| Row size | Small (hundreds of bytes) | Wide (many columns) |
| Storage | Row-oriented (PostgreSQL, MySQL) | Columnar (Parquet, Redshift) |
| Concurrency | Thousands of short transactions | Few long-running queries |
| Examples | MySQL, PostgreSQL, MongoDB | BigQuery, Snowflake, Redshift |
50. What is Apache Iceberg and why is it gaining adoption?
Apache Iceberg is an open table format for huge analytic datasets on object storage:
| Feature | Benefit |
|---|---|
| ACID transactions | Safe concurrent writes from multiple engines |
| Schema evolution | Add/rename/reorder columns without data rewrite |
| Partition evolution | Change partitioning without full table rewrite |
| Time travel | SELECT * FROM table FOR SYSTEM_TIME AS OF '2024-01-01' |
| Row-level deletes | MERGE INTO and DELETE WHERE without full rewrite |
| Multi-engine | Spark, Flink, Trino, DuckDB, BigQuery all support it |
# Spark + Iceberg
spark.sql("""
CREATE TABLE catalog.db.orders
USING ICEBERG
PARTITIONED BY (months(order_ts))
AS SELECT * FROM staging.orders
""")
# Time travel
spark.sql("""
SELECT * FROM catalog.db.orders
FOR SYSTEM_TIME AS OF TIMESTAMP '2024-01-01 00:00:00'
""")
Common mistakes
| Mistake | Why it's a problem | Fix |
|---|---|---|
Using SELECT * in warehouse queries |
Scans all columns; expensive on columnar storage | Select only needed columns |
| No watermark in streaming | State grows unbounded → OOM | Always set a watermark |
| Storing secrets in Airflow variables | Visible in UI | Use Airflow Connections or a secrets backend (Vault, AWS Secrets Manager) |
collect() on large Spark DF |
Pulls all data to driver → OOM | Use write, show(n), or aggregations |
Skipping is_incremental() check in dbt |
Full refresh every run; wastes compute | Always wrap filter in {% if is_incremental() %} |
| Using Python UDFs in Spark | Serialisation overhead vs JVM | Replace with built-in Spark functions or Pandas UDFs |
| No idempotency key | Duplicate rows on pipeline retry | Deduplicate on natural key in Silver layer |
| Ignoring data skew | One task runs for hours | Profile partition sizes; salt keys or broadcast |
Data engineer vs related roles
| Role | Focus | Primary tools |
|---|---|---|
| Data Engineer | Pipelines, infrastructure, data modeling | Spark, Airflow, dbt, SQL, Python |
| Data Scientist | Modelling, experimentation, statistics | Python (sklearn, PyTorch), notebooks |
| Data Analyst | Business insights, BI, dashboards | SQL, Tableau, Looker, Excel |
| ML Engineer | Deploy ML models to production | Docker, Kubernetes, MLflow, FastAPI |
| Analytics Engineer | dbt models, data mart design | dbt, SQL, Looker |
| Platform Engineer | Data platform infrastructure | Terraform, Kubernetes, Kafka, cloud |
Frequently asked questions
Q: What languages do data engineers use?
Python (primary), SQL (essential), Scala (Spark internals), sometimes Java. Shell scripting for ops tasks.
Q: Do I need to know Spark for a data engineering role?
At most companies: yes. Spark is the dominant distributed processing engine. At smaller companies, you may work with dbt + SQL only. Know Spark concepts even if you use managed tools like Databricks or AWS Glue.
Q: What is the difference between a data engineer and an analytics engineer?
An analytics engineer (popularised by dbt) focuses on the transformation layer inside the warehouse — writing dbt models, maintaining star schemas, and collaborating with analysts. A data engineer builds the broader infrastructure: ingestion, streaming, orchestration, and the raw storage layer.
Q: How do you choose between Airflow and Prefect/Dagster?
All three orchestrate Python workflows. Airflow has the largest ecosystem and is battle-tested. Prefect offers a simpler Python-native API. Dagster focuses on data assets (software-defined assets) with excellent observability. For large teams with complex DAGs: Airflow. For modern Python-first teams: Prefect or Dagster.
Q: What is the role of dbt in a modern data stack?
dbt handles the T in ELT — it runs inside the warehouse, version-controls SQL transformations, provides testing, documentation, and lineage. It doesn't ingest data (that's Fivetran/Airbyte) and doesn't orchestrate (that's Airflow/Prefect), but it integrates with both.
Q: What certifications are valuable for data engineers?
- Cloud: AWS Data Engineer Associate, GCP Professional Data Engineer, Azure Data Engineer Associate (DP-203)
- Databricks: Databricks Certified Data Engineer Professional
- dbt: dbt Analytics Engineering Certification
- Snowflake: SnowPro Core Certification