Data Warehouse Interview Questions and Answers
Data warehouse interviews ask how the platform behaves, not just what terms mean. Six questions answered on millions of real New York taxi trips: why columnar files are smaller, how partition pruning skips data, why reloads duplicate rows, what schema drift looks like, where stray rows go, and when to summarise.
GloryTecks Team
Training & Placement Faculty
Last updated: 8 October 2026
Data warehouse interview questions often start with definitions, such as OLTP versus OLAP or a fact table, and then move to behaviour. Why is this query cheap and that one expensive? What happens when a load runs twice? What breaks when the source adds a column? These answers are shown on real data, with DuckDB standing in for a warehouse engine, so you can describe what you have seen rather than what you have read.
The data is New York City yellow taxi trips for January 2024 and January 2025, published monthly by the Taxi and Limousine Commission (NYC TLC: trip record data), almost three million trips in the first month alone.
import os
import shutil
import duckdb
db = duckdb.connect()
JAN24, JAN25 = "yellow_tripdata_2024-01.parquet", "yellow_tripdata_2025-01.parquet" # NYC TLC yellow taxi trips
print("W1 row storage vs columnar storage: the same January 2024 trips")
db.execute(f"COPY (SELECT * FROM '{JAN24}') TO 'trips.csv' (HEADER)")
mb = lambda f: os.path.getsize(f) / 1e6
print(f" CSV {mb('trips.csv'):,.0f} MB | Parquet {mb(JAN24):,.0f} MB | rows {db.sql(f'SELECT COUNT(*) FROM {JAN24!r}').fetchone()[0]:,}")
os.remove("trips.csv")
print("\nW2 partition pruning: one day out of a month partitioned by pickup date")
shutil.rmtree("lake", ignore_errors=True)
db.execute(f"""COPY (SELECT *, CAST(tpep_pickup_datetime AS DATE) AS pickup_date FROM '{JAN24}'
WHERE tpep_pickup_datetime >= '2024-01-01' AND tpep_pickup_datetime < '2024-02-01')
TO 'lake' (FORMAT parquet, PARTITION_BY (pickup_date))""")
plan = db.execute("""EXPLAIN ANALYZE SELECT COUNT(*) FROM read_parquet('lake/*/*.parquet', hive_partitioning = true)
WHERE pickup_date = DATE '2024-01-15'""").fetchall()[0][1]
print(" partition folders:", len(os.listdir("lake")), "|", next(l.strip("│ ") for l in plan.splitlines() if "Scanning Files" in l))
print("\nW3 reloading a day: plain INSERT vs delete-then-insert")
db.execute("CREATE TABLE fact_trips AS SELECT * FROM read_parquet('lake/*/*.parquet', hive_partitioning = true) WHERE false")
load_day = "INSERT INTO fact_trips SELECT * FROM read_parquet('lake/*/*.parquet', hive_partitioning = true) WHERE pickup_date = DATE '2024-01-15'"
db.execute(load_day)
db.execute(load_day) # the job is retried after a failure
print(f" rows for 15 January after two plain inserts: {db.sql('SELECT COUNT(*) FROM fact_trips').fetchone()[0]:,}")
db.execute("DELETE FROM fact_trips")
for _ in range(2): # the same retry, made idempotent
db.execute("BEGIN")
db.execute("DELETE FROM fact_trips WHERE pickup_date = DATE '2024-01-15'")
db.execute(load_day)
db.execute("COMMIT")
print(f" rows after two delete-then-insert loads: {db.sql('SELECT COUNT(*) FROM fact_trips').fetchone()[0]:,}")
print("\nW4 schema drift between January 2024 and January 2025")
cols = lambda f: [r[0] for r in db.sql(f"DESCRIBE SELECT * FROM '{f}'").fetchall()]
print(" columns only in 2025:", sorted(set(cols(JAN25)) - set(cols(JAN24))))
print(" ", db.sql(f"""SELECT YEAR(tpep_pickup_datetime) AS year, COUNT(*) AS trips, COUNT(cbd_congestion_fee) AS with_cbd_fee
FROM read_parquet(['{JAN24}', '{JAN25}'], union_by_name = true)
WHERE MONTH(tpep_pickup_datetime) = 1 AND YEAR(tpep_pickup_datetime) IN (2024, 2025)
GROUP BY year ORDER BY year""").fetchall())
print("\nW5 rows in the January 2024 file that are not from January 2024")
print(" ", db.sql(f"""SELECT COUNT(*), MIN(tpep_pickup_datetime), MAX(tpep_pickup_datetime) FROM '{JAN24}'
WHERE tpep_pickup_datetime < '2024-01-01' OR tpep_pickup_datetime >= '2024-02-01'""").fetchone())
print("\nW6 a daily summary table answers daily questions from far fewer rows")
db.execute("""CREATE TABLE daily AS SELECT pickup_date, COUNT(*) AS trips, SUM(total_amount) AS revenue
FROM read_parquet('lake/*/*.parquet', hive_partitioning = true) GROUP BY pickup_date""")
raw = db.sql("SELECT ROUND(SUM(total_amount)) FROM read_parquet('lake/*/*.parquet', hive_partitioning = true) WHERE pickup_date BETWEEN '2024-01-08' AND '2024-01-14'").fetchone()[0]
summary = db.sql("SELECT ROUND(SUM(revenue)) FROM daily WHERE pickup_date BETWEEN '2024-01-08' AND '2024-01-14'").fetchone()[0]
print(f" summary rows {db.sql('SELECT COUNT(*) FROM daily').fetchone()[0]} | week of 8 January: raw {raw:,.0f} vs summary {summary:,.0f}")
shutil.rmtree("lake")W1 row storage vs columnar storage: the same January 2024 trips
CSV 299 MB | Parquet 50 MB | rows 2,964,624
W2 partition pruning: one day out of a month partitioned by pickup date
partition folders: 31 | Scanning Files: 1/31
W3 reloading a day: plain INSERT vs delete-then-insert
rows for 15 January after two plain inserts: 154,066
rows after two delete-then-insert loads: 77,033
W4 schema drift between January 2024 and January 2025
columns only in 2025: ['cbd_congestion_fee']
[(2024, 2964606, 0), (2025, 3475204, 3475204)]
W5 rows in the January 2024 file that are not from January 2024
(18, datetime.datetime(2002, 12, 31, 22, 59, 39), datetime.datetime(2024, 2, 1, 0, 1, 15))
W6 a daily summary table answers daily questions from far fewer rows
summary rows 31 | week of 8 January: raw 18,008,730 vs summary 18,008,730W1. Why do analytical warehouses store data by column?
Because analytical queries read a few columns from many rows, and columnar storage lets them read only those columns, compressed. The same January trips took 299 MB as CSV and 50 MB as Parquet, a column-oriented format built for compression and encoding (Apache Parquet: overview). Values in one column are similar, so they compress well. Transactional databases store whole rows together because they mostly read and write one record at a time. That is the practical difference behind "OLTP versus OLAP".
W2. What is partition pruning?
Splitting a table by a column such as date into separate files or folders, so that a query filtering on that column only opens the matching partitions (DuckDB: hive partitioning). Partitioned by pickup date, the month became 31 folders, and a query for 15 January scanned 1 of 31 files. BigQuery, Snowflake, Redshift and lakehouse tables all have a version of this, under names such as partitioning, clustering and micro-partitions.
The trap to mention: pruning only works when the filter is on the partition column itself. Wrap it in a function or filter on a different date column, and every partition is read again.
W3. What happens if a load job runs twice?
With a plain INSERT, the data is loaded twice. Loading 15 January and then retrying, as an orchestrator does after a failure, left 154,066 rows instead of 77,033. Make loads idempotent: delete the partition being loaded and insert it again inside one transaction, or MERGE on a key. Two delete-then-insert runs left exactly 77,033 rows. Interviewers like this question because retries are normal in production, and a pipeline that cannot be rerun safely will eventually double-count.
W4. How do you handle a source that adds a column?
Expect it. From 2025 the TLC files include cbd_congestion_fee for New York's congestion pricing charge, a column the 2024 files do not have. Reading both with union_by_name lines columns up by name rather than position (DuckDB: combining schemas): all 3,475,204 trips from January 2025 have a value, while the 2,964,606 from January 2024 get NULL. A NULL for a column that did not exist yet is honest; a zero would claim that no fee was charged. In a warehouse, decide whether new source columns are added automatically or must pass review, and record when each column started.
W5. Where do rows from the wrong period go?
First find them. The January 2024 file holds 18 trips dated outside January, from 31 December 2002 to just after midnight on 1 February 2024. Some are clearly wrong clocks; others are late or boundary records. If the warehouse partitions by the date in the row, they land in partitions that may already be closed and reported. Decide the rule in advance: quarantine impossible dates, route late but valid rows to their true partition and reprocess it, and monitor the count. The 18 rows would also have created extra partitions if the load had not filtered to the month.
W6. When should you build summary tables?
When many queries ask the same aggregate question. A daily summary of trips and revenue has 31 rows instead of nearly three million, and the week of 8 January gave the same revenue from both, 18,008,730 dollars. The cost is keeping it in step with the facts: every reload or late row must also refresh the summary. Materialised views do this automatically in many warehouses. Say what refresh strategy you would use, not just that you would add the table.
How to answer warehouse questions
- Explain cost in terms of data read, such as columns, partitions and files.
- Treat every load as something that will be retried.
- Assume the source schema will change, and say how you would notice.
- Name a rule for late and invalid rows before they arrive.
For the concepts behind these answers, see data warehouse concepts and architecture and data lake vs data warehouse vs lakehouse. Loads are covered in idempotency in data pipelines and handling late-arriving data, and design questions in data modeling interview questions.
What are common data warehouse interview questions?
What is the difference between a data warehouse and a database?
What makes a data load idempotent?
How does partitioning speed up queries?
Warehouse design and loading are part of the Data Engineering course.
Sources
- Apache Parquet overview — The Apache Software Foundation
- Hive Partitioning (DuckDB documentation) — DuckDB
- Combining Schemas (DuckDB documentation) — DuckDB
- TLC Trip Record Data (NYC Taxi and Limousine Commission) — NYC Taxi and Limousine Commission
GloryTecks Team
Training & Placement Faculty
The GloryTecks faculty team mentors learners across Data Science, AI and Data Engineering, with a focus on real-time projects and placement readiness in Hyderabad.
Want to master Interview Questions?
Join GloryTecks’ expert-led Data Engineering training in Hyderabad with real-time projects and 100% placement support.
Related articles
Python Interview Questions for Data Roles
The plain-Python coding tasks data-analyst and data-engineer interviews use, without pandas: counting, deduplicating, grouping, streaming large files, flattening JSON, joining, and the bugs interviewers plant. Each has a worked solution and the follow-up questions to expect.
Data Science Interview Questions and Answers 2026
A data science interview is several interviews in one: statistics, machine learning, coding, product sense and a take-home. This guide maps each round with model answers, then works a churn take-home on real telecom data, including the checks that separate a strong submission from a high score.
AWS Interview Questions for Data Engineers
AWS interview questions for data engineers on S3, Glue, Athena, Redshift, Kinesis, Step Functions and security, each with the points a strong answer includes, the follow-up to expect, and three end-to-end scenario questions.