State the data grain, assumptions, metric, leakage or failure risk, and how you would validate the result before discussing tools.
Question set
11 detailed answers
01How does OLTP differ from OLAP, and why don't you run analytics on the production OLTP database?
middle
Short answer: OLTP serves short transactions — inserting and updating individual rows with low latency, while OLAP answers analytical queries that scan millions of rows. OLTP stores data row-wise, OLAP column-wise. You don't run heavy analytics on the production OLTP database because it competes for resources with transactions and tanks production latency.
In depth:
- Workload profile — OLTP: many small operations (
INSERT/UPDATEby primary key); OLAP: few heavy queries withGROUP BYand aggregations. - Storage model — row storage reads the whole row (good for transactions); columnar reads only the needed columns and compresses better (good for analytics).
- Normalization — OLTP is normalized (3NF) for write integrity; OLAP is denormalized for read speed.
- Resource isolation — an analytical scan floods the buffer cache and disk, so transactions start to wait.
| Criterion | OLTP | OLAP |
|---|---|---|
| Purpose | transactions | analytics |
| Operations | row reads/writes | column aggregations |
| Storage | row-wise | columnar |
| Normalization | 3NF | denormalized (star) |
| Metric | latency, TPS | scan throughput |
⚠️ Common mistake: running reports directly against the production database. Extract data into a separate warehouse (ETL/ELT) so analytics doesn't disturb transactions.
02What is dimensional modeling and the star schema, and why does star beat a fully normalized model for analytics?
middle
Short answer: Dimensional modeling splits data into fact tables (measurable business events) and dimension tables (context: who, what, when, where). A star is one fact table in the center surrounded by denormalized dimensions. It beats a fully normalized model for analytics because it needs fewer joins and is intuitive for analysts.
In depth:
- Fact table — holds numeric metrics (amount, quantity) and foreign keys to dimensions; many rows, few columns.
- Dimensions — descriptive attributes for filtering and grouping (date, product, customer); few rows, many columns.
- Grain — the level of detail of one fact row; declare it first.
- Why star — predictable fact→dimension joins, simple queries, and the optimizer handles it well.
┌────────────┐
│ dim_date │
└──────┬─────┘
│
┌────────────┐ ┌────┴───────┐ ┌──────────────┐
│ dim_product│───│ fact_sales │───│ dim_customer │
└────────────┘ │ metrics + │ └──────────────┘
│ dim FKs │
┌─┴────────────┘
│
┌─────┴──────┐
│ dim_store │
└────────────┘
⚠️ Common mistake: not declaring the grain. Without a clear grain, rows of different levels land in the fact table and metrics double-count on aggregation.
03Star vs snowflake schema — what's the difference, and what are the denormalization tradeoffs in a warehouse?
senior
Short answer: In a star, dimensions are denormalized — each dimension is one flat table. In a snowflake, dimensions are normalized and split into a hierarchy of related tables (product → category → department). The snowflake saves space and removes duplication but adds joins and complicates queries; on modern columnar warehouses star is usually preferred.
In depth:
- Star — a dimension keeps its whole hierarchy in one table (denormalized); fewer joins, simpler SQL, faster reads.
- Snowflake — the hierarchy is spread across several tables (normalized); less duplication, easier to maintain reference data, but more joins.
- Denormalization tradeoff — you pay in storage and drift risk for speed and simplicity. On columnar warehouses storage is cheap while extra joins are expensive — so star usually wins.
| Criterion | Star | Snowflake |
|---|---|---|
| Dimensions | denormalized | normalized |
| Joins | fewer | more |
| Storage | more | less |
| SQL complexity | lower | higher |
| Read speed | higher | lower |
⚠️ Common mistake: normalizing dimensions to save gigabytes on a warehouse where storage is cheap — paying for it with joins in every analytical query.
04What are Slowly Changing Dimensions (SCD), how do Type 1, 2, and 3 differ, and how do you implement SCD2?
senior
Short answer: SCD (Slowly Changing Dimensions) describe how to store changes to dimension attributes over time. Type 1 overwrites the value (no history), Type 2 creates a new row version (full history), Type 3 keeps the prior value in a separate column (limited history). SCD2 is the standard when you need to see how data looked at the time of an event.
In depth:
- Type 1 —
UPDATEover the old value. Simple, but history is lost. - Type 2 — a new row with a surrogate key, effective dates (
valid_from/valid_to) and anis_currentflag. Facts reference the version valid at the event date. - Type 3 — a
previous_valuecolumn. Keeps only one step back.
-- SCD2: close the old version; insert the new one as a separate INSERT
MERGE INTO dim_customer AS tgt
USING staging_customer AS src
ON tgt.customer_nk = src.customer_nk
AND tgt.is_current = TRUE
WHEN MATCHED AND tgt.city <> src.city THEN
UPDATE SET tgt.valid_to = CURRENT_DATE,
tgt.is_current = FALSE
WHEN NOT MATCHED THEN
INSERT (customer_sk, customer_nk, city,
valid_from, valid_to, is_current)
VALUES (GENERATE_SK(), src.customer_nk, src.city,
CURRENT_DATE, DATE '9999-12-31', TRUE);
⚠️ Common mistake: using SCD1 where you need history. Overwriting a customer's city retroactively rewrites every past report too — the event binds to the new value.
05What are the fact table types (transaction, periodic snapshot, accumulating snapshot), and why declare the grain first?
middle
Short answer: There are three fact table types: transaction (one row per event), periodic snapshot (a slice of metrics per period — day, month) and accumulating snapshot (one row per process, updated as it moves through stages). The type dictates the grain, and you declare the grain before choosing columns.
In depth:
- Transaction — a row per event (sale, click); the most detailed grain, grows fastest.
- Periodic snapshot — a regular slice (stock balance at end of day); rows aren't updated, great for trends.
- Accumulating snapshot — a row per process instance (an order), with stage-date columns filled as it progresses (created → paid → shipped); the row is updated.
- Why grain first — the grain defines what one row means; without it, metrics of different granularity mix and double-count.
| Type | Grain | Update | Example |
|---|---|---|---|
| Transaction | event | append-only | a sale |
| Periodic | period | append-only | end-of-day balance |
| Accumulating | process | updated | order lifecycle |
⚠️ Common mistake: designing fact columns before declaring the grain — later you find the metrics can't be summed without double-counting.
06Data warehouse vs data lake vs lakehouse — what's each, when do you use which, and what does the lakehouse add?
senior
Short answer: A data warehouse stores structured, modeled data for BI with a strict schema (schema-on-write). A data lake stores raw files of any format cheaply on object storage (schema-on-read). A lakehouse adds a layer with ACID transactions and tables (Delta/Iceberg/Hudi) on top of the lake, combining the lake's cheap storage with the warehouse's reliability and performance.
In depth:
- Warehouse — Snowflake/BigQuery/Redshift; data is cleaned and modeled, excellent BI and SQL, but pricier and worse for raw data and ML.
- Data lake — S3/GCS with Parquet/JSON; cheap and flexible, stores everything, but without transactions it easily turns into a data swamp.
- Lakehouse — table formats (Delta Lake, Apache Iceberg, Hudi) over object storage; provide ACID, time travel, and schema enforcement directly on files.
| Property | Warehouse | Lake | Lakehouse |
|---|---|---|---|
| Data | structured | any | any |
| Schema | on-write | on-read | on-write/read |
| ACID | yes | no | yes |
| Storage cost | higher | lower | lower |
| Workload | BI/SQL | ML/raw | BI + ML |
⚠️ Common mistake: dumping raw data into a lake with no catalog, schema, or transactions — a year later it's an unmanageable data swamp nobody can query.
07Surrogate keys vs natural keys in a warehouse — why surrogate keys, and how do they interact with SCD and joins?
middle
Short answer: A natural key is a business identifier from the source (email, tax ID, order number). A surrogate key is an artificial technical identifier (usually an integer auto-increment) carrying no meaning. In a warehouse, dimensions are keyed by surrogates: they're stable, compact in joins, and — crucially — let you store multiple versions of one business entity under SCD2.
In depth:
- Stability — a source natural key can change (a customer changes email); a surrogate never does.
- SCD2 — under versioning one natural key spawns several rows, so the dimension's primary key must be the surrogate.
- Performance — an integer surrogate is more compact and faster in joins than a composite or string natural key.
- Source isolation — swapping the source system doesn't break facts that reference surrogates.
CREATE TABLE dim_customer (
customer_sk BIGINT PRIMARY KEY, -- surrogate key
customer_nk VARCHAR NOT NULL, -- natural key from source
email VARCHAR,
valid_from DATE NOT NULL,
valid_to DATE NOT NULL,
is_current BOOLEAN NOT NULL
);
-- the fact references the surrogate, not the business key
-- fact_sales.customer_sk -> dim_customer.customer_sk
⚠️ Common mistake: making the natural key the primary key of an SCD2 dimension — once a second row version appears, the key is no longer unique.
08How does the Kimball approach differ from Inmon, and where do modern ELT/dbt marts fit in?
middle
Short answer: Inmon builds top-down: first a normalized enterprise warehouse (3NF) as the single source of truth, then marts from it. Kimball builds bottom-up: dimensional marts (stars) right away, tied together by conformed dimensions (the bus). Inmon is pricier and slower up front but more consistent; Kimball delivers business value faster. Modern ELT on dbt usually leans Kimball.
In depth:
- Inmon (top-down) — a central normalized EDW feeding dependent marts; strong integrity, high cost of entry.
- Kimball (bottom-up) — a set of stars with conformed dimensions; fast time-to-value, integration via the dimension bus.
- Where ELT/dbt fits — raw data is loaded into the warehouse (ELT), and dbt transformations build staging → intermediate models → dimensional marts. In spirit it's Kimball, with elements of both.
| Criterion | Kimball | Inmon |
|---|---|---|
| Direction | bottom-up | top-down |
| Core | stars/marts | normalized EDW (3NF) |
| Time-to-value | fast | slow |
| Integrity | via conformed dims | centralized |
| Cost of entry | lower | higher |
⚠️ Common mistake: building independent marts with no conformed dimensions — you get silos whose metrics can't be compared.
09What is Data Vault modeling (hubs, links, satellites), and what problem does it solve versus a star schema?
senior
Short answer: Data Vault is a modeling method for integrating data from many sources with full history and auditability. It splits data into three entity types: hubs (business keys), links (relationships between keys), and satellites (descriptive attributes with history). Unlike a star, Data Vault is optimized not for analyst queries but for flexible loading and data lineage.
In depth:
- Hub — the unique business keys of an entity (customer_id) plus load metadata; the stable core.
- Link — a many-to-many relationship table between hubs (order ↔ customer).
- Satellite — context and attributes attached to a hub or link, with change history (like SCD2).
- Why — you can plug in new sources without reworking the model, everything is historized and auditable. Analysts build star marts on top of the Vault.
┌───────────┐ ┌───────────┐
│ HUB │ │ HUB │
│ customer │ │ order │
└─────┬─────┘ └─────┬─────┘
│ ┌──────┐ │
└──────┤ LINK ├──────┘
└──┬───┘
┌─────────┐ │ ┌─────────┐
│ SAT │◄─┴─►│ SAT │
│ attrs │ │ attrs │
└─────────┘ └─────────┘
⚠️ Common mistake: handing Data Vault to analysts as a query mart — its many joins make it awkward for BI; you still build dimensional marts on top of it.
10One Big Table (wide denormalized tables) vs star schema on modern columnar warehouses — what's the tradeoff?
middle
Short answer: One Big Table (OBT) is a single wide denormalized table where facts are already joined to all dimension attributes, with no joins on read. On modern columnar warehouses, where unused columns aren't scanned, OBT gives very fast, simple queries. The price is data duplication, bloat when dimensions change, and loss of dimension reusability.
In depth:
- OBT — everything in one table; zero joins, simple SQL, ideal for dashboards and BI tools. But changing a dimension attribute must be smeared across every row.
- Star — facts + separate dimensions; reusable dimensions, cheaper storage, cleaner SCD, but joins are required.
- When OBT — stable dimensions, speed and simplicity matter, a columnar engine (BigQuery/Snowflake). When star — many shared dimensions, active SCD, storage savings.
| Criterion | OBT | Star |
|---|---|---|
| Joins | none | yes |
| Duplication | high | low |
| SCD | awkward | natural |
| Dimension reuse | no | yes |
| Read speed | maximum | high |
⚠️ Common mistake: using OBT for fast-changing dimensions — every attribute change forces rewriting millions of fact rows instead of one row in a dimension.
11How do partitioning and clustering/sort keys work in a warehouse, and how do they cut scan cost?
middle
Short answer: Partitioning physically splits a table into parts by a column's value (usually a date) so a query scans only the needed partitions — that's partition pruning. Clustering (a sort key) orders data within a partition by frequently filtered columns so the engine skips irrelevant blocks. Together they cut the scan volume, and thus query time and cost.
In depth:
- Partitioning — splitting by date/region; a filter on the partition key prunes irrelevant partitions. Choose a column that's almost always in the
WHERE. - Clustering / sort key — sorting data within a partition; using block metadata the engine skips blocks that don't match the filter (block/zone skipping).
- Granularity — too-fine partitions (hourly) spawn tiny files and overhead; too-coarse ones prune poorly.
-- BigQuery: partition by date + clustering
CREATE TABLE sales.fact_orders
PARTITION BY DATE(order_ts) -- prune by day
CLUSTER BY customer_id, product_id -- skip blocks within a day
AS SELECT * FROM staging.orders;
-- the query scans just 1 partition out of thousands:
SELECT SUM(amount)
FROM sales.fact_orders
WHERE order_ts >= '2026-06-01'
AND customer_id = 42;
⚠️ Common mistake: partitioning by a high-cardinality column (e.g. customer_id) — you get thousands of tiny partitions, which slows queries down rather than speeding them up.
Source notes
References and review policy
RecallDeck’s interview answers are editorial material, reviewed against maintained official documentation where a primary reference is available. Tool selections use direct provider links and contain no affiliate placements. Features can change after the review date.
From reading to recall
Practice the full interview loop.
RecallDeck schedules the concepts you miss and keeps coding, design, and behavioral fundamentals available when the interviewer changes direction.