Memory Atlas Β· Data processing

Databricks

Build a durable mental model of how Spark plans, moves, and optimizes distributed workβ€”then retrieve it under interview pressure.

Chapters
07
Advanced
05
Mode
Recall

Read for structure. Pause at each memory map and answer before revealing the detail.

Foundation

Databricks Overview and Interview Atlas

#

Databricks Overview and Interview Atlas

Answer First: Databricks is a managed data and AI platform that combines Apache Spark compute, governed data assets, workflow orchestration, SQL, and production tooling.

Memory Map: workspace -> compute -> data -> governance -> workflows -> operations.

Delta ownership: This module keeps Databricks integration context concise. Transaction-log, MERGE, time-travel, CDF, and maintenance internals are canonical on /learn/delta.

Version and product-name guardrails

  • The current documentation calls Delta Live Tables Lakeflow Spark Declarative Pipelines. Legacy DLT wording remains searchable because interviews and existing systems still use it: official guide.
  • The current documentation calls Databricks Asset Bundles Declarative Automation Bundles. Treat bundle syntax and supported resources as versioned platform behavior: official guide.
  • Predictive optimization applies to eligible Unity Catalog managed tables; verify workspace, cloud, and feature prerequisites before promising it: official guide.
  • Legacy notes often say that DROP TABLE immediately deletes managed data. The current Unity Catalog managed-table documentation uses an 8-day retention period after DROP before permanent deletion; Hive metastore and older-runtime behavior can differ: official guide.
  • Defaults, product availability, prices, UI labels, and benchmark multipliers in the legacy notes are snapshots or anecdotes. Confirm them in the target workspace and cloud documentation.

Canonical Databricks module map

Use the chapter navigation in order, or jump from the link-only question index to the exact concept owner.

Databricks + PySpark Interview Preparation β€” Master Index & Mind Map

πŸ’‘ Interview Tip
Covers: Beginner to Architect level β€” everything you need for any data engineering interview Last Updated: 2026-02-27 Total Questions: 200+ across all files

MIND MAP β€” Topics & Coverage

πŸ—‚οΈDATABRICKS INTERVIEW
1SPARK ARCHITECTURE & INTERNALSFile: 01
Application Lifecycle (Driver, Executors, Cluster Manager)
DAG Scheduler vs Task Scheduler
Narrow vs Wide Dependencies
Catalyst Optimizer (4 Phases)
Tungsten & Whole-Stage CodeGen
Adaptive Query Execution (AQE)
Memory Management (Unified Model)
Execution vs Storage Memory
On-Heap vs Off-Heap
OOM Debugging (Driver vs Executor)
Shuffle Deep Dive
Shuffle Write (Map Side)
Shuffle Read (Reduce Side)
External Shuffle Service
FetchFailedException
Serialization (Java vs Kryo vs Tungsten vs Arrow)
Fault Tolerance & Speculation
2PYSPARK ADVANCED OPERATIONSFile: 02
DataFrame API Deep Dive
select vs withColumn vs selectExpr
withColumn Anti-Pattern
Lazy Evaluation Benefits
Joins (5 Physical Strategies)
Broadcast Hash Join
Sort-Merge Join
Shuffle Hash Join
BroadcastNestedLoop Join
Cartesian Product
Window Functions
row_number vs rank vs dense_rank
Running Totals, Moving Averages
rowsBetween vs rangeBetween
Data Skew Handling
Salting Technique
AQE Skew Join
Isolate-and-Union
Two-Phase Aggregation
UDFs & Performance
Row-at-a-Time UDF (Slow)
Pandas UDF / Vectorized UDF
mapInPandas
Partitioning
repartition vs coalesce
Hash vs Range Partitioning
Bucketing
Caching & Checkpointing
Structured Streaming
Micro-Batch vs Continuous
Output Modes (Append/Complete/Update)
Watermarking
Stream-Stream Joins
foreachBatch Pattern
Exactly-Once Semantics
State Store Management
Coding Challenges (15+ Problems)
3DELTA LAKE & LAKEHOUSEFile: 03
Delta Lake Internals
Transaction Log (_delta_log)
Checkpoint Files
Optimistic Concurrency Control
File-Level Statistics & Data Skipping
Isolation Levels
MERGE INTO β€” All Scenarios
Basic MERGE
Handling Duplicate Keys
MERGE Performance Optimization
Schema Evolution with MERGE
Time Travel & Recovery
OPTIMIZE, VACUUM, ZORDER
Z-Ordering vs Liquid Clustering
Deletion Vectors
Schema Evolution
Clone Operations (Deep vs Shallow)
Table Properties (Important Settings)
Managed vs External Tables
Lakehouse Architecture
Data Lake vs Warehouse vs Lakehouse
Key Enabling Technologies
4ETL SCENARIOS & PIPELINE DESIGNFile: 04
SCD Implementations
SCD Type 1 (Overwrite)
SCD Type 2 (History β€” Full Code)
SCD Type 3 (Previous Value)
Merge Key Trick Explained
Change Data Capture (CDC)
Debezium CDC Pipeline Design
Delta Lake Change Data Feed (CDF)
CDC Event Types
Medallion Architecture (Bronze/Silver/Gold)
Design Decisions per Layer
Data Quality Between Layers
When to Deviate
Late-Arriving Data Handling
Auto Loader
Directory Listing vs File Notification
Schema Evolution Modes
Auto Loader vs COPY INTO
Delta Live Tables (DLT)
Expectations (3 Levels)
SCD Type 2 with apply_changes
Materialized View vs Streaming Table
COMPLETE vs TRIGGERED Mode
Scenario-Based Pipeline Design (10+ Scenarios)
Clickstream 10B events/day
8-hour Pipeline Optimization
Data Mesh on Databricks
Oracle CDC to Delta Lake
Batch + Streaming Unified
Data Quality Framework
500K Small Files Fix
Streaming Pipeline Falling Behind
2-Year Backfill
Multi-Tenant Architecture
3-Hour MERGE Optimization
Real-Time Fraud Detection
Pipeline Health Monitoring
5PERFORMANCE TUNING & PRODUCTIONFile: 05
Reading Spark Execution Plans
Spark UI Debugging Methodology
Key Metrics to Monitor
Partition Tuning
spark.sql.shuffle.partitions
Optimal Partition Size
Small File Problem
Join Optimization
All Join Strategies Compared
Broadcast Threshold Tuning
Dynamic Partition Pruning
Spill Debugging
Predicate Pushdown Verification
File Format Selection
Unity Catalog
Three-Level Namespace
Row-Level & Column-Level Security
Data Lineage
Storage Credentials & External Locations
Delta Sharing
Hive Metastore Migration
Photon Engine
When It Helps
When It Doesn't
Databricks Workflows & Orchestration
Workflows vs Airflow
Job Cluster vs All-Purpose
Task Parameter Passing
Databricks Asset Bundles (CI/CD)
Cost Management Strategies
CI/CD for Databricks Pipelines
Data Governance at Scale
System Design Questions (Leadership Level)
6DELTA LAKE ADVANCED MASTERCLASSFile: DB_06
OPTIMIZE Deep Dive
Internal Mechanics (bin-packing vs Z-ORDER)
When OPTIMIZE Hurts (5 anti-patterns)
Cost Analysis
Liquid Clustering vs Z-ORDER vs Partitioning
Complete Comparison Table
Incremental vs Full Rewrite (key insight)
Hilbert Curve vs Z-Order Curve
Migration Path
Deletion Vectors Internals
RoaringBitmap Mechanics
Read/Write Trade-offs
DV + MERGE Interaction
Change Data Feed (CDF/CDC)
4 Change Types
Streaming CDF Patterns
5 Gotchas (not retroactive, INSERT OVERWRITE, etc.)
UniForm (Universal Format)
Delta + Iceberg + Hudi Metadata
Feature Asymmetry Table
One-Way Sync Limitation
Predictive Optimization
3 Levels: Optimized Writes vs Auto Compact vs PO
Unity Catalog Requirement
VACUUM Retention Gotcha
Small File Problem (Complete Playbook)
6 Root Causes
7 Solutions
VACUUM Risks & Production Incidents
4 Real Incident Scenarios
Production Best Practices Checklist
Delta vs Iceberg vs Hudi
Feature Comparison Matrix
Decision Framework
Hidden Partitioning Deep Dive
Rapid-Fire Interview Questions (10+)
Protocol Versions (irreversible upgrades)
Schema Enforcement vs Evolution
WAP Pattern, Row Tracking, Type Widening, VARIANT
Gotcha Questions with Traps
TOPIC FREQUENCY IN REAL INTERVIEWS
πŸ”΄ Very High: Delta MERGE/SCD2, Performance Tuning, Medallion Architecture
🟠 High: Data Skew, Streaming, Unity Catalog, Auto Loader, AQE, OPTIMIZE/ZORDER
🟑 Medium-High: DLT, Window Functions, CI/CD, Photon
🟒 Medium: Liquid Clustering, Deletion Vectors, Delta Sharing

QUESTION TRACKER (Prevents Duplicates)

Already Covered Topics (DO NOT RE-ADD):

  • Spark application lifecycle
  • DAG Scheduler vs Task Scheduler
  • Narrow vs Wide dependencies
  • Catalyst optimizer (4 phases)
  • AQE (3 optimizations)
  • Whole-stage codegen & Tungsten
  • Unified memory management
  • OOM debugging (driver vs executor)
  • Shuffle process (write/read)
  • External shuffle service
  • FetchFailedException
  • Serialization options (Java/Kryo/Arrow/Tungsten)
  • Accumulators & Broadcast variables
  • Speculative execution
  • Block Manager
  • DataFrame select/withColumn/selectExpr
  • withColumn anti-pattern
  • All 5 join strategies
  • Data skew handling (4 techniques)
  • Window functions (row_number/rank/dense_rank)
  • Running total, moving average, pct_of_total
  • rowsBetween vs rangeBetween
  • repartition vs coalesce
  • Hash vs Range partitioning
  • Bucketing
  • cache/persist/checkpoint
  • UDF types & performance
  • Pandas UDF / mapInPandas
  • Structured Streaming model
  • Output modes (append/complete/update)
  • Watermarking
  • Stream-stream joins
  • foreachBatch pattern
  • Exactly-once semantics
  • State store management
  • Delta transaction log
  • Checkpoint files
  • Optimistic concurrency control
  • File-level statistics
  • MERGE INTO (all scenarios)
  • Time travel
  • OPTIMIZE / VACUUM / ZORDER
  • Z-Ordering vs Liquid Clustering
  • Deletion vectors
  • Schema evolution
  • Deep clone vs Shallow clone
  • Managed vs External tables
  • Delta table properties
  • SCD Type 1, 2, 3 (full code)
  • Merge key trick
  • CDC patterns (Debezium, CDF)
  • Medallion architecture
  • Auto Loader (modes, schema evolution)
  • DLT (expectations, apply_changes)
  • 12+ scenario-based pipeline designs
  • Spark UI debugging
  • Partition tuning
  • Small file problem
  • Predicate pushdown
  • Dynamic partition pruning
  • Spill debugging
  • Unity Catalog (all aspects)
  • Photon engine
  • Databricks Workflows
  • Asset Bundles / CI/CD
  • Cost management
  • Data governance
  • System design (leadership)
  • Py4J gateway architecture
  • Cost-based optimization (CBO)
  • Coding challenges (15+ problems)

HOW TO USE THIS PREPARATION

Answer First: Day 1-2: File 01 (Architecture) + File 03 (Delta Lake) β€” understand the foundation.

Memory Map: If you have 1 week -> seven-day schedule sequences architecture foundations -> middle sessions deepen pipelines and governance -> final rehearsal consolidates production scenarios -> daily checkpoints confirm coverage [00_MASTER_INDEX.md:304].

If you have 1 week:

  1. Day 1-2: File 01 (Architecture) + File 03 (Delta Lake) β€” understand the foundation
  2. Day 3-4: File 04 (ETL Scenarios) β€” practice SCD Type 2 code, CDC pipelines
  3. Day 5-6: File 02 (PySpark) β€” coding challenges, streaming, joins
  4. Day 7: File 05 (Production) β€” Unity Catalog, tuning, system design

Answer First: Day 1: File 03 + File 04 (Delta Lake + ETL β€” most asked topics).

Memory Map: If you have 3 days -> three-day schedule prioritizes core architecture -> second block combines ETL and Delta mechanics -> final block rehearses production trade-offs -> timed review reveals remaining gaps [00_MASTER_INDEX.md:310].

If you have 3 days:

  1. Day 1: File 03 + File 04 (Delta Lake + ETL β€” most asked topics)
  2. Day 2: File 01 + File 02 (Architecture + PySpark coding)
  3. Day 3: File 05 (Production + system design)

Answer First: Focus on: SCD Type 2 code, MERGE scenarios, Medallion architecture, Data skew handling, AQE, Unity Catalog, and the scenario-based questions in File 04.

Memory Map: If you have 1 day -> single-day triage selects highest-frequency concepts -> focused passes alternate Spark and Delta -> scenario drills force concise decisions -> final recall check exposes weak areas [00_MASTER_INDEX.md:315].

If you have 1 day:

Focus on: SCD Type 2 code, MERGE scenarios, Medallion architecture, Data skew handling, AQE, Unity Catalog, and the scenario-based questions in File 04.

KEY INTERVIEW PATTERNS FOR 10+ YEARS EXPERIENCE

  1. They won't ask "What is Spark?" β€” They'll ask "Walk me through how you'd debug a 4-hour job that's failing on a 10 TB dataset"
  2. Scenario > Definition β€” "Design a CDC pipeline from Oracle to Delta Lake" > "What is CDC?"
  3. Trade-offs > Single Answer β€” "When would you NOT use broadcast join?" > "What is a broadcast join?"
  4. Architecture > Code β€” "How would you design a multi-tenant data platform?" > "Write a SELECT query"
  5. Depth > Breadth β€” They'll drill into YOUR answers. If you mention Z-Ordering, expect "How does it work internally with space-filling curves?"

4-Day Azure Databricks Interview Prep

πŸ’‘ Interview Tip
Focus: Azure Databricks ONLY (PySpark covered separately) Created: 2026-03-24 Interview Window: By 2026-03-28

REAL-WORLD CONTEXT (Frame Every Answer With This)

  • Domain: Travel industry β€” flight bookings, pricing, passenger data (PII/GDPR)
  • Scale: Billions of transactions/day, many airlines, 100+ countries
  • Cloud: Microsoft Azure
  • Legacy: Oracle databases, Java-based core systems β†’ migrating to modern data stack
  • Key Concerns: Data governance (GDPR), low-latency pricing, CDC from legacy systems, cost at scale
  • Typical Stack: Databricks, Azure Synapse, Azure DevOps, Kafka, Hadoop/Hive, BigQuery, Sqoop

Tip: Always use travel domain examples β€” bookings, passenger records, flight schedules, fare pricing, loyalty programs

4-DAY SCHEDULE

πŸ—ΊοΈMemory Map
DAY 16-7 hoursDELTA LAKE + LAKEHOUSE FOUNDATION
Delta Lake Internals (transaction log, checkpoints, ACID)
MERGE INTO β€” all scenarios + optimization
OPTIMIZE / VACUUM / Z-ORDER / Liquid Clustering
Deletion Vectors, Schema Evolution, Table Properties
Time Travel & Recovery
Lakehouse Architecture (vs Data Lake vs Warehouse)
NEW 2025-2026: Delta Lake 4.x features, Predictive Optimization
DAY 26-7 hoursETL PATTERNS + PIPELINE DESIGN
Medallion Architecture (Bronze/Silver/Gold) β€” design decisions
SCD Type 1, 2, 3 implementation in Databricks SQL
CDC patterns (Oracle β†’ Kafka β†’ Delta Lake)
Auto Loader (modes, schema evolution, vs COPY INTO)
Lakeflow Declarative Pipelines (formerly DLT)
Scenario-Based Pipeline Design (travel domain)
Pipeline Monitoring & Data Quality Framework
DAY 36-7 hoursAZURE DATABRICKS PLATFORM + GOVERNANCE
Unity Catalog (hierarchy, RBAC, row/column security, lineage)
NEW: ABAC (Attribute-Based Access Control)
NEW: Predictive Optimization
Delta Sharing (open protocol, cross-org)
Photon Engine (when to use, when not)
NEW: Serverless Compute & Serverless Workspaces
Azure-Specific: ADLS Gen2, Key Vault, Service Principals
Managed vs External Tables in Unity Catalog
Data Governance at Scale (GDPR, PII masking)
DAY 45-6 hoursPRODUCTION, CI/CD, COST + MOCK INTERVIEWS
Databricks Workflows & Orchestration (vs Airflow)
Declarative Automation Bundles (formerly Asset Bundles) β€” CI/CD
Azure DevOps + Databricks integration
Job Cluster vs All-Purpose vs Serverless cost comparison
Cost Management Strategies
NEW: Multi-table Transactions, Lakebase, Compatibility Mode
System Design Questions (leadership-level)
Production Debugging Scenarios
MOCK: 10 most-likely interview questions

PRIORITY MATRIX

Answer First: Delta Lake MERGE + SCD Type 2 (write code).

Memory Map: MUST KNOW (Will definitely be asked β€” 60%) -> interview frequency elevates essential topics -> architecture and Delta mechanics anchor answers -> production scenarios connect decisions to evidence -> mock scoring verifies must-know readiness [DB_00_4DAY_AZURE_DATABRICKS_PLAN.md:74].

MUST KNOW (Will definitely be asked β€” 60%)

  1. Delta Lake MERGE + SCD Type 2 (write code)
  2. Medallion Architecture β€” design decisions per layer
  3. Unity Catalog β€” 3-level namespace, security model
  4. Auto Loader + Lakeflow Declarative Pipelines basics
  5. CDC pipeline design (Oracle β†’ Kafka β†’ Delta Lake)
  6. OPTIMIZE / VACUUM / Z-ORDER vs Liquid Clustering
  7. Performance Tuning β€” Spark UI, partition tuning, data skew

Answer First: Job Cluster vs All-Purpose vs Serverless

Memory Map: SHOULD KNOW (High probability β€” 25%) -> probability weighting schedules secondary topics -> governance and optimization extend core answers -> targeted drills attach operational examples -> remaining time depends on score gaps [DB_00_4DAY_AZURE_DATABRICKS_PLAN.md:83].

SHOULD KNOW (High probability β€” 25%)

  1. Photon Engine β€” when it helps/doesn't
  2. Databricks Workflows vs Airflow
  3. Job Cluster vs All-Purpose vs Serverless
  4. Azure integration (ADLS Gen2, Key Vault, Service Principals)
  5. Data Governance / GDPR handling
  6. CI/CD with Declarative Automation Bundles
  7. Schema Evolution, Change Data Feed

Answer First: ABAC (Attribute-Based Access Control) β€” new 2025.

Memory Map: NICE TO KNOW (Differentiators β€” 15%) -> differentiator topics follow core mastery -> emerging features add senior-level context -> concise examples demonstrate informed judgment -> optional review protects primary preparation [DB_00_4DAY_AZURE_DATABRICKS_PLAN.md:92].

NICE TO KNOW (Differentiators β€” 15%)

  1. ABAC (Attribute-Based Access Control) β€” new 2025
  2. Serverless Workspaces (GA Jan 2026)
  3. Lakebase (GA Azure Mar 2026)
  4. Multi-table Transactions (BEGIN ATOMIC)
  5. Delta Lake 4.x features (Variant type, Type Widening)
  6. Predictive Optimization
  7. Iceberg interoperability via Unity Catalog

LEARNING APPROACH

Since you are NEW to Databricks but experienced in data engineering, every question follows this pattern:

🧠 INTERVIEW TIP β†’ How to answer this confidently
WHAT IS IT?β†’Simple 2-3 line explanation in plain English
WHY DO WE NEED IT?β†’Real problem it solves (with travel/booking example)
HOW DOES IT WORK?β†’Technical details + code with comments on every line
WHEN TO USE / NOT USE?β†’Practical decision guide
INTERVIEW TIPHow to answer this confidently

You learn the basics WHILE studying the interview questions β€” no separate basics doc needed.

Every code block has comments explaining WHAT each line does and WHY. Every concept has a real-world analogy (like a travel booking system).

HOW TO USE

  1. Read the DB_ file for each day β€” basics are embedded inside each question
  2. Every code block has comments β€” read the comments to understand the basics
  3. For deeper PySpark details, refer to your existing files (01, 02)
  4. Practice answering OUT LOUD β€” senior interviews test communication
  5. Frame every answer with Real-world context: "In a travel booking pipeline..."
  6. Day 4 Mock questions β€” time yourself, 3-5 minutes per answer
  7. Key interview pattern: SCENARIO > DEFINITION. They won't ask "What is Unity Catalog?" β€” they'll ask "How would you govern PII data across 50 teams?"

Azure Databricks β€” Question Bank (L1/L2/L3)

Advanced

Spark on Databricks

#

Databricks Spark Runtime

Answer First: Spark fundamentals still determine Databricks performance: reason from the logical plan to stages, shuffles, tasks, executors, and observable evidence in the Spark UI.

Memory Map: plan -> stages -> shuffle -> tasks -> executors -> Spark UI.

Delta ownership: This module keeps Databricks integration context concise. Transaction-log, MERGE, time-travel, CDF, and maintenance internals are canonical on /learn/delta.

Spark Architecture & Internals

πŸ’‘ Interview Tip
Focus: Deep internals, debugging, trade-offs β€” from fundamentals to architect-level Approach: Every topic starts with simple explanation β†’ then interview-level depth
🧠 D β†’ Driver (the boss who plans the work)
MEMORY MAP: SPARK ARCHITECTURE→DCE-SAT
DDriver (the boss who plans the work)
CCluster Manager (HR β€” allocates workers)
EExecutors (workers who do the actual processing)
SStages (assembly lines separated by shuffles)
AAQE (Adaptive Query Execution β€” auto-optimizer)
TTungsten (memory manager β€” off-heap, binary format)

SECTION 1: SPARK APPLICATION LIFECYCLE

Answer First: spark-submit starts the driver, which creates the SparkSession and builds a lazy DAG. An action creates a job; shuffle boundaries divide stages, tasks run on executors, and completion events return to the driver.

Memory Map: the complete lifecycle of a Spark application from spark-submit to job completion -> spark-submit launches the driver process -> driver builds a lazy DAG and jobs -> scheduler divides stages into executor tasks -> completion events close the application [01_Spark_Architecture_and_Internals.md:24].

Q1: Explain the complete lifecycle of a Spark application from spark-submit to job completion.

Simple Explanation: Think of a Spark application as opening a restaurant kitchen for one big dinner service. Here is what happens step by step:

  1. The Head Chef (Driver) arrives and sets up the kitchen plan (SparkSession).
  2. The Head Chef calls the Restaurant Manager (Cluster Manager) and says "I need 10 line cooks tonight."
  3. The Restaurant Manager hires and assigns Line Cooks (Executors) to their stations.
  4. The Head Chef looks at the full dinner menu (your code) and plans the most efficient way to prepare all the dishes β€” this is the logical plan.
  5. A GPS navigation system (Catalyst Optimizer) finds the fastest route through the recipe β€” the physical plan.
  6. The Head Chef breaks the dinner into courses (Stages) β€” appetizer, then main, then dessert. Each course boundary is a "serving moment" (shuffle).
  7. Within each course, individual prep tasks are assigned β€” one per ingredient batch (one per partition).
  8. Tasks are handed to the line cooks, who execute them, report back, and the meal is served.

Technical Answer:

  1. The driver process starts and creates a SparkContext/SparkSession
  2. SparkContext connects to the Cluster Manager (YARN/Mesos/K8s/Standalone)
  3. Cluster Manager allocates executor JVMs on worker nodes
  4. Driver converts user code into a logical plan (DAG of DataFrame operations)
  5. The Catalyst Optimizer optimizes the logical plan β†’ physical plan
  6. The DAG Scheduler breaks the physical plan into stages at shuffle boundaries
  7. Each stage is broken into tasks (one per partition) by the Task Scheduler
  8. Tasks are serialized and sent to executors
  9. Executors run tasks, store results, and report back to the driver
  10. Results are collected or written to storage
# Example: a simple Spark application lifecycle in code
from pyspark.sql import SparkSession
spark = SparkSession.builder \
.appName("RestaurantKitchen") \ # ← Name your application (the dinner service)
.config("spark.executor.memory", "8g") \ # ← Each line cook gets 8 GB workspace
.getOrCreate() # ← Head Chef opens the kitchen (Driver starts)
df = spark.read.parquet("s3://orders/") # ← Read the ingredient list (lazy β€” no work yet!)
result = df.filter(df.status == "active") \ # ← Plan: only use fresh ingredients (still lazy)
.groupBy("region") \ # ← Plan: group dishes by region (will cause a shuffle = new stage)
.count() # ← Plan: count dishes per region (still lazy)
result.write.parquet("s3://output/") # ← ACTION! Now the kitchen actually starts cooking
# ^ This single action triggers: logical plan→Catalyst optimization → DAG → stages → tasks → execution

Interview Tip: They love to ask "walk me through what happens when you call .write()" β€” trace the full path from action to DAG to stages to tasks.

What NOT to Say: "Spark executes each line of code as you write it." No β€” Spark is lazy. Nothing happens until an action triggers the full pipeline.

Follow-up they'll ask: "What happens if the driver dies vs an executor dies?" β†’ See Q5.

Answer First: The DAG Scheduler is the trip planner β€” it looks at your full itinerary and divides it into legs: "First drive from NYC to Philadelphia (Stage 1), then Philadelphia to Baltimore (Stage 2)." Each stop where you refuel is a shuffle boundary.

Memory Map: the difference between the DAG Scheduler and the Task Scheduler -> difference between dag scheduler and task scheduler distinguishes scheduler ownership before stage formation -> action creates a job DAG -> shuffle boundaries divide stages -> partitions become tasks -> scheduler events expose execution order [01_Spark_Architecture_and_Internals.md:76].

Q2: What is the difference between the DAG Scheduler and the Task Scheduler?

Simple Explanation: Think of it like planning a road trip vs driving it.

The DAG Scheduler is the trip planner β€” it looks at your full itinerary and divides it into legs: "First drive from NYC to Philadelphia (Stage 1), then Philadelphia to Baltimore (Stage 2)." Each stop where you refuel is a shuffle boundary.

The Task Scheduler is the actual driver β€” for each leg of the trip, it decides: "Which lane should I be in? Should I take the highway or the side road?" It assigns individual driving tasks to available cars (executors), trying to pick the car closest to the data (data locality).

Technical Answer:

AspectDAG SchedulerTask Scheduler
Operates atStage levelTask level (within a stage)
ResponsibilityComputes DAG of stages, identifies shuffle dependenciesAssigns tasks to executors with data locality
HandlesStage retries on fetch failuresIndividual task retries, speculative execution
InputLogical plan / physical planTaskSet (set of tasks for one stage)
LocalityN/APROCESS_LOCAL > NODE_LOCAL > RACK_LOCAL > ANY

Interview Tip: They may ask "How does Spark decide which executor runs which task?" β€” the answer is the Task Scheduler's locality-aware scheduling. It prefers the executor where data already lives.

What NOT to Say: "The DAG Scheduler assigns tasks to executors." No β€” the DAG Scheduler only creates stages. The Task Scheduler handles the actual assignment.

Answer First: Narrow dependency = cars staying in their own lane. Each car (partition) goes straight ahead without merging.

Memory Map: narrow vs wide dependencies with examples. Why does this distinction matter -> narrow vs wide dependencies with examples this distinction matter identifies the exact shuffle boundary and failure mode -> dependency determines redistribution -> map output crosses the exchange -> reducers fetch and sort partitions -> UI metrics reveal skew, spill, or failure [01_Spark_Architecture_and_Internals.md:101].

Q3: Explain narrow vs wide dependencies with examples. Why does this distinction matter?

Simple Explanation: Imagine a highway.

  • Narrow dependency = cars staying in their own lane. Each car (partition) goes straight ahead without merging. No one needs to cross lanes. This is fast β€” no coordination needed. Examples: map, filter, union.

  • Wide dependency = a highway merge where cars from ALL lanes need to reorganize. Every car might need to move to a different lane based on its destination. This merge point is a shuffle β€” everyone has to slow down, signal, and reposition. Examples: groupByKey, join, repartition.

The merge is the most expensive part of the trip. Minimizing merges (shuffles) is the #1 Spark performance optimization.

Technical Answer:

  • Narrow dependency: Each parent partition is used by at most ONE child partition

    • Examples: map, filter, union, coalesce (reduce only)
    • Can be pipelined within a single stage (no shuffle)
  • Wide dependency: Each parent partition may be used by MULTIPLE child partitions

    • Examples: groupByKey, reduceByKey, join (non-co-partitioned), repartition
    • Requires a shuffle β†’ creates a new stage boundary
python β€” editable
# Narrow dependencies β€” all pipelined in ONE stage, no shuffle
df = spark.read.parquet("s3://sales/")        # ← Read data (Stage 0 starts)
df2 = df.filter(df.amount > 100)              # ← Narrow: each partition filters independently
df3 = df2.withColumn("tax", df2.amount * 0.1) # ← Narrow: each partition adds column independently
# All three operations above are fused into a SINGLE stage β€” no data movement!

# Wide dependency β€” triggers a shuffle, creates a NEW stage
df4 = df3.groupBy("region").sum("amount")     # ← Wide: data must be redistributed by region
# ^ This is the highway merge β€” records with same region must go to the same partition
# Stage 0 ends here, shuffle happens, Stage 1 begins

df4.write.parquet("s3://output/")             # ← Action triggers everything

Why it matters: Wide dependencies trigger the most expensive operation in Spark β€” shuffle. Every shuffle means data serialization β†’ disk write β†’ network transfer β†’ disk read β†’ deserialization. Understanding this helps you minimize shuffles in your pipelines.

Interview Tip: They will ask "How do you reduce shuffles in a pipeline?" Answer: (1) filter early, (2) use broadcast joins, (3) co-partition data, (4) use reduceByKey instead of groupByKey.

What NOT to Say: "All joins cause shuffles." Not true β€” broadcast joins and co-partitioned joins avoid shuffles entirely.

Answer First: The Block Manager tracks cached and shuffle blocks on each executor, stores them in memory or disk, and serves local or remote block requests while reporting locations to the driver.

Memory Map: the Block Manager and how does it work -> executor registers blocks with its manager -> memory or disk stores partition replicas -> peer and driver requests locate cached data -> eviction and transfer metrics expose block state [01_Spark_Architecture_and_Internals.md:144].

Q4: What is the Block Manager and how does it work?

Simple Explanation: Think of the Block Manager as a warehouse shelf system in each executor. Every executor has its own warehouse, and the warehouse stores:

  • Cached data (items you need again soon β€” kept on the closest shelf)
  • Shuffle data (items being shipped to other warehouses)
  • Broadcast variables (company memos β€” one copy per warehouse)

There is also a central inventory tracker on the driver (BlockManagerMaster) that knows exactly which warehouse has which item. When Executor A needs a shuffle block from Executor B, it asks the central tracker "Where is block X?", gets the address, then fetches it directly.

Technical Answer: BlockManager is the storage subsystem in each executor (and the driver). It manages:

  • Cached/persisted RDD/DataFrame partitions
  • Shuffle data (shuffle blocks)
  • Broadcast variable blocks
  • Task result blocks

Architecture:

  • MemoryStore: On-heap and off-heap memory
  • DiskStore: Local disk spillover
  • BlockManagerMaster (on driver): Tracks all block locations across the cluster
  • BlockTransferService (Netty-based): Fetches blocks from remote executors

When a shuffle reader needs a block from another executor, it queries BlockManagerMaster for the location, then uses BlockTransferService to fetch it.

Interview Tip: If asked about shuffle internals or caching, mention Block Manager β€” it shows you understand the storage layer beneath the abstractions.

What NOT to Say: "Cached data is stored on HDFS." No β€” cached data lives in executor memory/local disk, managed by Block Manager, not on distributed storage.

Answer First: An executor failure loses its in-memory partitions and active tasks, so Spark reschedules the work and recomputes missing data from lineage. A driver failure ends coordination and usually requires application restart, with streaming state recovered from checkpoints.

Memory Map: What happens when a driver fails vs when an executor fails -> driver loss removes scheduling authority -> executor loss invalidates local blocks and tasks -> cluster manager replaces available processes -> lineage and retry policy determine recovery [01_Spark_Architecture_and_Internals.md:175].

Q5: What happens when a driver fails vs when an executor fails?

Simple Explanation: Back to our restaurant analogy:

If a line cook (executor) gets sick and goes home:

  • The Head Chef notices (heartbeat timeout).
  • The Head Chef reassigns that cook's dishes to other cooks.
  • If that cook had already prepped something (shuffle output), the prep might need to be redone β€” unless you have a pantry system (External Shuffle Service) that kept the prep work safe.

If the Head Chef (driver) collapses:

  • The entire kitchen shuts down. Nobody knows the plan anymore.
  • In client mode: dinner service is cancelled. No recovery.
  • In cluster mode: the Restaurant Manager can hire a new Head Chef, but the new chef has to start the plan from scratch β€” all in-progress work is lost.

Technical Answer:

Executor failure:

  • Driver detects via heartbeat timeout
  • Tasks on that executor are rescheduled on other executors
  • If the stage used shuffle output from the lost executor, those shuffle blocks must be recomputed
  • Cached RDD partitions on that executor are lost β†’ recomputed on demand
  • The External Shuffle Service mitigates this (shuffle data survives executor death)

Driver failure:

  • Client mode: Entire application fails. No recovery.
  • Cluster mode: With spark.driver.supervise=true (Standalone) or YARN --max-app-attempts, the driver restarts, but ALL state is lost (SparkContext, accumulators, broadcast variables)
  • Structured Streaming: Can recover from driver failure using checkpointing (offsets + state are persisted)

Interview Tip: Always mention the External Shuffle Service when discussing executor failure β€” it shows you know production-grade Spark.

What NOT to Say: "If the driver fails, executors continue working." No β€” executors cannot function without the driver. The driver is the brain.

SECTION 2: CATALYST OPTIMIZER

Answer First: Catalyst parses expressions, resolves tables and types, rewrites the logical plan, and compares physical strategies before Spark executes the selected plan.

Memory Map: the complete pipeline of Spark's Catalyst Optimizer -> complete pipeline of spark s catalyst optimizer selects the Catalyst phase that explains the observed plan -> expression enters parser -> analyzer resolves names and types -> optimizer rewrites the logical plan -> planner selects executable operators [01_Spark_Architecture_and_Internals.md:212].

Q6: Explain the complete pipeline of Spark's Catalyst Optimizer.

Simple Explanation: Catalyst is like a GPS navigation system for your query. You type in your destination (SQL query or DataFrame code), and the GPS:

  1. Parses your input β€” "OK, you want to go from Home to Airport" (understands the request).
  2. Analyzes β€” "Let me verify 'Home' and 'Airport' are real places" (resolves table/column names).
  3. Optimizes the route β€” "Taking the highway is faster than side streets" (predicate pushdown, column pruning).
  4. Plans the physical drive β€” "Should I take Route A or Route B? Let me check traffic (statistics)" (chooses join strategies, picks best physical plan).
  5. Generates turn-by-turn directions β€” "Left in 200m, then merge right" (Tungsten code generation β€” optimized bytecode).

Just like GPS, the more information it has (traffic = table statistics), the better route it picks.

Technical Answer:

🧠 Memory Map
SQL/DataFrame API
↓
1. PARSING→Unresolved Logical Plan (AST)
↓
2. ANALYSIS→Resolved Logical Plan
(Analyzer resolves table names, column names, data types using Catalog)
↓
3. LOGICAL OPTIMIZATION→Optimized Logical Plan
Rule-based optimizations:
β€’ Predicate pushdown
β€’ Constant folding
β€’ Column pruning (projection pushdown)
β€’ Boolean simplification
β€’ Null propagation
β€’ Filter/projection combining
↓
4. PHYSICAL PLANNING→Physical Plan(s)
β€’ Generates multiple candidate plans (e.g., SortMergeJoin vs BroadcastHashJoin)
β€’ Cost model selects the best plan
β€’ Uses table/column statistics if available (CBO)
↓
5. CODE GENERATION (Tungsten) β†’ Optimized Java Bytecode
β€’ Whole-stage code generation
β€’ Fuses operators into tight loops
β€’ Avoids virtual function dispatch
# See the Catalyst pipeline in action
df = spark.read.parquet("s3://sales/") # ← Read source data
result = df.filter(df.year == 2024) \ # ← Filter (Catalyst will push this down!)
.select("region", "amount") \ # ← Project (Catalyst will prune unused columns!)
.groupBy("region").sum("amount") # ← Aggregate
result.explain(True) # ← Show ALL 4 Catalyst phases
# Output shows:
# == Parsed Logical Plan == ← Step 1: raw AST
# == Analyzed Logical Plan == ← Step 2: columns/tables resolved
# == Optimized Logical Plan == ← Step 3: filter pushed down, columns pruned
# == Physical Plan == ← Step 4: HashAggregate chosen, codegen enabled

Key insight for interviews: Catalyst is why DataFrame operations are faster than RDD operations β€” the optimizer can reason about the operations and optimize the entire plan globally.

Interview Tip: If asked "Why are DataFrames faster than RDDs?", the answer is Catalyst + Tungsten. RDDs are opaque β€” Spark cannot optimize what it cannot see inside.

What NOT to Say: "Catalyst only works with SQL queries." No β€” it optimizes both SQL and DataFrame API calls identically. They both go through the same pipeline.

Answer First: Predicate pushdown = Instead of bringing ALL books to your desk and then searching, you tell the librarian "I only want books from 2024" and the librarian only brings you the 2024 shelf. The filter is pushed down to the source.

Memory Map: Predicate Pushdown and Projection Pushdown? When do they NOT work -> filter and column references reach the data source -> supported predicates and projections become scan requirements -> unsupported expressions remain Spark operators -> physical plan and scan metrics prove pushdown [01_Spark_Architecture_and_Internals.md:276].

Q7: What is Predicate Pushdown and Projection Pushdown? When do they NOT work?

Simple Explanation: Imagine you are looking for a specific book in a library.

Predicate pushdown = Instead of bringing ALL books to your desk and then searching, you tell the librarian "I only want books from 2024" and the librarian only brings you the 2024 shelf. The filter is pushed down to the source.

Projection pushdown = Instead of photocopying the entire book, you say "I only need chapters 3 and 7." The librarian only copies those chapters. The column selection is pushed down to the source.

Both reduce the amount of data that ever enters Spark's processing pipeline.

Technical Answer:

  • Predicate pushdown: Filters pushed as close to the data source as possible (into Parquet file metadata, JDBC WHERE clause). Reduces I/O dramatically.
  • Projection pushdown: Only required columns are read from source. Columnar formats (Parquet) benefit hugely β€” entire column chunks are skipped.

When they DON'T work:

  1. UDFs in filter conditions β€” Catalyst cannot reason about UDF internals, so predicates involving UDFs cannot be pushed down
  2. Complex nested column access β€” may not be pushed in all Spark versions
  3. Non-optimized data sources that don't support pushdown
  4. After a shuffle β€” predicates before the shuffle cannot be pushed past it
python β€” editable
# Predicate pushdown WORKS here:
df = spark.read.parquet("s3://sales/")
df.filter(df.year == 2024).select("region", "amount")
# ← Spark pushes "year == 2024" into the Parquet reader
# ← Parquet skips entire row groups where year != 2024

# Predicate pushdown BROKEN here:
from pyspark.sql.functions import udf
is_valid = udf(lambda x: x > 0)              # ← UDF is a black box to Catalyst
df.filter(is_valid(df.amount)).select("region", "amount")
# ← Catalyst CANNOT push this filter down β€” it doesn't know what the UDF does
# ← ALL data is read, THEN filtered in Python β€” much slower!

How to verify: Use df.explain(True) β€” look for PushedFilters in the scan node.

Interview Tip: When discussing performance tuning, always mention checking explain() for pushed filters. If filters are not being pushed, it usually means a UDF is blocking optimization.

What NOT to Say: "Predicate pushdown always works automatically." It does not β€” UDFs, certain data sources, and complex expressions can prevent it.

Answer First: Adaptive Query Execution revises the physical plan from runtime statistics, so it can coalesce shuffle partitions, switch join strategies, and split skewed partitions after execution begins.

Memory Map: Adaptive Query Execution (AQE)? What problems does it solve -> adaptive query execution aqe problems it solve supplies the statistic that can revise physical execution -> statistics establish the initial strategy -> runtime observations revise joins or partitions -> reduced exchange and scan work -> final plan metrics prove adaptation [01_Spark_Architecture_and_Internals.md:320].

Q8: What is Adaptive Query Execution (AQE)? What problems does it solve?

Simple Explanation: Remember our GPS analogy for Catalyst? Catalyst is like a GPS that plans your route before you start driving β€” based on estimated traffic. But what if there is an accident on the highway that the GPS did not know about?

AQE is a GPS that recalculates your route mid-drive based on actual traffic. It watches what is happening during execution and adjusts the plan on the fly. If a partition turns out to be tiny, AQE merges it. If a table turns out to be small, AQE switches to a broadcast join. If one lane is jammed (data skew), AQE splits the traffic.

Technical Answer: AQE (default ON in Spark 3.x) re-optimizes the query plan at runtime based on actual shuffle statistics. It solves 3 problems:

1. Coalescing post-shuffle partitions:

  • If many shuffle partitions are tiny, AQE merges them
  • Config: spark.sql.adaptive.coalescePartitions.enabled=true
  • Eliminates the pain of tuning spark.sql.shuffle.partitions

2. Converting Sort-Merge Join β†’ Broadcast Hash Join:

  • If one side of a join turns out to be small at runtime (< spark.sql.adaptive.autoBroadcastJoinThreshold)
  • Happens when compile-time statistics were wrong

3. Optimizing skew joins:

  • Detects skewed partitions at runtime
  • Splits the large partition and replicates the corresponding partition from the other side
  • Config: spark.sql.adaptive.skewJoin.enabled=true
python β€” editable
# AQE in action β€” you don't need to do anything special, just enable it
spark.conf.set("spark.sql.adaptive.enabled", "true")             # ← Enable AQE (default in Spark 3.x)
spark.conf.set("spark.sql.adaptive.coalescePartitions.enabled", "true")  # ← Auto-merge small partitions
spark.conf.set("spark.sql.adaptive.skewJoin.enabled", "true")    # ← Auto-handle skew

# Before AQE: you set shuffle.partitions=200 and pray it's right
# After AQE: set it HIGH (e.g., 2000) and let AQE merge small partitions automatically
spark.conf.set("spark.sql.shuffle.partitions", "2000")           # ← Intentionally high β€” AQE will reduce

# AQE also fixes this common issue:
# Catalyst plans a SortMergeJoin because it THINKS table B is 500 MB
# But after filtering, table B is actually only 5 MB
# AQE detects this mid-execution β†’ switches to BroadcastHashJoin β†’ much faster!

Scenario question: "You set spark.sql.shuffle.partitions=200 but your 100 GB shuffle creates many tiny partitions. How does AQE help?" β†’ AQE auto-coalesces the 200 partitions into fewer, larger partitions.

Interview Tip: AQE is THE modern answer to most Spark tuning questions. If asked "How do you tune shuffle partitions?", say "Set it high and let AQE coalesce. Manual tuning is legacy."

What NOT to Say: "I manually tune spark.sql.shuffle.partitions for each job." That is the pre-Spark 3.0 approach. AQE makes manual tuning largely unnecessary.

Answer First: Tungsten's whole-stage codegen collapses an entire stage of operators (filter β†’ project β†’ aggregate) into a single Java function.

Memory Map: Whole-Stage Code Generation (CodeGen) -> compatible operators form one generated pipeline -> Java source fuses row processing loops -> JVM compilation removes virtual-call overhead -> explain output marks whole-stage boundaries [01_Spark_Architecture_and_Internals.md:368].

Q9: What is Whole-Stage Code Generation (CodeGen)?

Simple Explanation: Think of Tungsten's codegen like organizing your desk for maximum efficiency. Instead of reaching for a different tool for each step (open drawer, grab scissors, close drawer, open another drawer, grab tape...), you lay out everything you need in one line and process items in one smooth motion.

Without codegen, Spark processes data by calling one operator at a time β€” filter calls next() on scan, project calls next() on filter, etc. Each call has overhead (like opening/closing drawers). With codegen, Spark fuses all operators into a single tight loop β€” one function that does scan+filter+project in one pass. No overhead between steps.

Technical Answer: Tungsten's whole-stage codegen collapses an entire stage of operators (filter β†’ project β†’ aggregate) into a single Java function.

Traditional Volcano/iterator model:

  • Each operator calls next() on its child
  • Virtual function dispatch per row
  • Poor CPU cache utilization
  • Branch prediction misses

With CodeGen:

  • Fuses operators into tight loops
  • No virtual function calls
  • Operates on raw memory (sun.misc.Unsafe)
  • Leverages CPU pipelining and L1/L2 cache
  • Can see generated code: df.queryExecution.debug.codegen()
python β€” editable
# See codegen in action
df = spark.read.parquet("s3://sales/")
result = df.filter(df.amount > 100).select("region", "amount").groupBy("region").sum("amount")

# Check if codegen is being used:
result.explain()
# Look for "WholeStageCodegen" in the plan output
# *(1) HashAggregate ← the * means codegen is active for this operator
# +- *(1) Filter      ← same codegen stage β€” fused into one function!
# +- *(1) ColumnarToRow
# +- FileScan parquet  ← scan is outside codegen (it's I/O-bound anyway)

# View the actual generated Java code (for debugging):
# result.queryExecution.debug.codegen()  ← Scala/SparkShell only

What breaks codegen:

  • External sorts
  • Some joins with complex expressions
  • Python UDFs (completely bypass codegen)
  • Very large expressions (hit JVM method size limit)

Interview Tip: If asked "Why are Python UDFs slow?", mention that they bypass Tungsten codegen entirely. Suggest Pandas UDFs (Arrow-based) as the alternative.

What NOT to Say: "CodeGen makes all operations faster." It does not help I/O-bound operations, and Python UDFs bypass it completely.

Answer First: If Catalyst's rule-based optimizer is a GPS that follows fixed rules ("always prefer highways"), then CBO is like the GPS checking real-time traffic data before choosing a route.

Memory Map: Cost-Based Optimization (CBO) -> catalog table and column statistics estimate cardinality -> cardinality model compares candidate physical operators -> planner selects the lowest estimated plan -> EXPLAIN output exposes estimates and choice [01_Spark_Architecture_and_Internals.md:420].

Q10: What is Cost-Based Optimization (CBO)?

Simple Explanation: If Catalyst's rule-based optimizer is a GPS that follows fixed rules ("always prefer highways"), then CBO is like the GPS checking real-time traffic data before choosing a route.

CBO uses statistics about your data β€” how many rows, how many distinct values, min/max values β€” to make smarter decisions. Without statistics, Spark guesses. With statistics, Spark knows.

Technical Answer: CBO uses table and column statistics to choose optimal plans.

Collect statistics:

sql
ANALYZE TABLE t COMPUTE STATISTICS;                    -- ← Collect table-level stats (row count, size)
ANALYZE TABLE t COMPUTE STATISTICS FOR COLUMNS c1, c2; -- ← Collect column-level stats (distinct count, min, max, nulls, histogram)

Statistics collected:

  • Table: row count, size in bytes
  • Column: distinct count, min, max, avg length, null count, histogram

What CBO affects:

  • Join strategy selection (broadcast vs sort-merge)
  • Join ordering in multi-table joins
  • Filter selectivity estimation

Enable:

python β€” editable
spark.conf.set("spark.sql.cbo.enabled", "true")               # ← Enable cost-based optimization
spark.conf.set("spark.sql.cbo.joinReorder.enabled", "true")    # ← Let CBO reorder multi-way joins
python β€” editable
# See CBO in action:
result.explain("cost")   # ← Shows the plan WITH cost estimates
# Look for "Statistics(sizeInBytes=500.0 MiB, rowCount=1.00E+7)" in the output
# If you see "Statistics(sizeInBytes=None)" β€” CBO has no stats! Run ANALYZE TABLE.

Interview Tip: If asked "Your broadcast join threshold is 10 MB, but Spark is still doing sort-merge join on a 5 MB table. Why?", the answer is: Spark does not know the table is 5 MB because statistics have not been collected. Run ANALYZE TABLE.

What NOT to Say: "CBO is always on by default and works automatically." You need to explicitly collect statistics and enable it.

SECTION 3: MEMORY MANAGEMENT

Answer First: Reserved corner (300 MB) β€” always occupied by essentials (pens, stapler). You cannot use this for work.

Memory Map: Spark's Unified Memory Management model in detail -> spark s unified memory management model in detail locates pressure in the relevant Spark memory pool -> partition demand consumes execution memory -> cached blocks compete for the shared region -> spill or garbage collection signals pressure -> executor metrics locate the constrained pool [01_Spark_Architecture_and_Internals.md:468].

Q11: Explain Spark's Unified Memory Management model in detail.

Simple Explanation: Think of each executor's memory as an office desk. The desk has fixed zones:

  1. Reserved corner (300 MB) β€” always occupied by essentials (pens, stapler). You cannot use this for work.
  2. Personal area (User Memory, 40%) β€” your personal stuff: sticky notes, coffee mug (UDF variables, RDD metadata).
  3. Work area (Spark Unified Memory, 60%) β€” the actual work surface, split into two halves:
    • Active project zone (Execution) β€” papers you are actively working on right now (shuffles, joins, sorts)
    • Reference shelf (Storage) β€” documents you might need again soon (cached DataFrames, broadcast variables)

The key insight: the boundary between Execution and Storage is flexible. If you are doing a massive join and need more desk space, Execution can push Storage papers aside. But Storage CANNOT push active work aside β€” you cannot pause mid-calculation.

Technical Answer:

πŸ“ Architecture Diagram
Executor JVM Heap
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Reserved Memory (300 MB, fixed)             β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ User Memory                                β”‚
β”‚ (1 - spark.memory.fraction) * (heap-300 MB) β”‚
β”‚ Default: 40% of (heap - 300 MB)             β”‚
β”‚ Used for: UDF variables, RDD metadata      β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Spark (Unified) Memory                     β”‚
β”‚ spark.memory.fraction * (heap - 300 MB)     β”‚
β”‚ Default: 60% of (heap - 300 MB)             β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”         β”‚
β”‚ β”‚  Execution   β”‚    Storage      β”‚         β”‚
β”‚ β”‚  (shuffles,  β”‚    (cached      β”‚         β”‚
β”‚ β”‚   joins,     β”‚     data,       β”‚         β”‚
β”‚ β”‚   sorts)     β”‚     broadcast)  β”‚         β”‚
β”‚ β”‚              β”‚                 β”‚         β”‚
β”‚ β”‚ ← soft boundary, can borrow β†’ β”‚         β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜         β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Key rule: Execution can evict Storage (cached data), but Storage cannot evict Execution. This is because execution memory is critical (can't pause mid-computation), while cached data can be recomputed.

python β€” editable
# Example: 8 GB executor
# Reserved:      300 MB (fixed, untouchable)
# Usable heap:   8192 - 300 = 7892 MB
# Spark memory:  7892 * 0.6 = 4735 MB  ← for execution + storage
# User memory:   7892 * 0.4 = 3157 MB  ← for your UDF variables, metadata
# Execution:     4735 * 0.5 = 2368 MB  ← initial split (flexible!)
# Storage:       4735 * 0.5 = 2368 MB  ← initial split (flexible!)

Interview Tip: Draw this diagram on the whiteboard. Interviewers love it. Emphasize the "soft boundary" and why Execution wins over Storage.

What NOT to Say: "Execution and Storage memory are fixed, separate pools." That was the OLD model (Static Memory Management, pre-Spark 1.6). The Unified model has a flexible boundary.

Answer First: Executor memory = the main office room (JVM heap)

Memory Map: all memory-related configurations and their relationship -> executor heap divides reserved and unified regions -> storage and execution borrow shared memory -> overhead controls native and Python processes -> spill and garbage collection reveal sizing errors [01_Spark_Architecture_and_Internals.md:524].

Simple Explanation: Think of it like renting an office space. You need to understand what you are paying for:

  • Executor memory = the main office room (JVM heap)
  • Memory overhead = the hallway, lobby, and bathrooms (off-heap: VM internals, network buffers)
  • Container size = total rent = office room + hallway (YARN allocates the whole thing)

If you only configure the office room but forget the hallway, YARN kills your container for using more space than you paid for.

Technical Answer:

python β€” editable
# Executor heap β€” the main workspace
spark.executor.memory = "8g"           # ← JVM heap (your office room)

# Memory overhead β€” off-heap, for VM internals, NIO buffers, etc.
spark.executor.memoryOverhead = "2g"    # ← Default: max(384 MB, 10% of executor.memory)
# Container size = executor.memory + memoryOverhead = 10g  ← total YARN container

# Spark memory fraction β€” how much of the heap Spark gets
spark.memory.fraction = 0.6            # ← 60% of (heap - 300 MB) for Spark operations
spark.memory.storageFraction = 0.5     # ← Initial 50-50 split between execution and storage

# Off-heap memory (optional, avoids GC entirely)
spark.memory.offHeap.enabled = true    # ← Enable Tungsten off-heap memory
spark.memory.offHeap.size = "4g"       # ← Size of off-heap pool (outside JVM heap)

# Driver memory
spark.driver.memory = "4g"             # ← Driver JVM heap
spark.driver.maxResultSize = "1g"      # ← Max size of collect() results β€” prevents driver OOM

Interview Tip: The most common production issue is YARN killing containers. Always mention memoryOverhead β€” it is the most overlooked config.

What NOT to Say: "Just increase spark.executor.memory to fix OOM errors." Sometimes the issue is overhead, sometimes it is skew. Diagnose first.

Answer First: OOM (Out of Memory) errors are the 1 reason Spark jobs fail in production. There are two kinds.

Memory Map: What causes OOM errors? How do you debug driver OOM vs executor OOM -> driver heap pressure affects planning or result collection -> executor pressure occurs inside task memory -> logs and GC metrics locate the failing process -> sizing or partition repair targets that pool [01_Spark_Architecture_and_Internals.md:562].

Q13: What causes OOM errors? How do you debug driver OOM vs executor OOM?

Simple Explanation: OOM (Out of Memory) errors are the #1 reason Spark jobs fail in production. There are two kinds:

Driver OOM = The Head Chef's desk overflows. Usually because someone called collect() and tried to bring the entire warehouse to the chef's tiny desk. Or a broadcast variable is too large to fit in the driver's memory.

Executor OOM = A line cook's station overflows. Usually because one cook got an unfairly large portion of data (skew), or there is not enough memory for a massive shuffle/join.

Technical Answer:

Driver OOM causes:

CauseSolution
collect() on large dataUse take(n), show(), or write to storage
Large broadcast variableCheck variable size, increase spark.driver.memory
Too many tasks (metadata)Reduce number of partitions
Accumulator resultsLimit accumulator usage
toPandas() on large DFUse Arrow + batch conversion

Executor OOM causes:

CauseSolution
Skewed partition (1 partition >> others)Salt keys, use AQE, broadcast join
Insufficient memory for shuffle/joinIncrease spark.executor.memory, increase partitions
UDF holding large objectsMove large objects to broadcast variables
Container killed by YARNIncrease spark.executor.memoryOverhead

Debugging checklist:

python β€” editable
# Step 1: Check which executor died
# β†’ Spark UI β†’ Executors tab β†’ look for "Removed" executors

# Step 2: Check the error type
# "java.lang.OutOfMemoryError: Java heap space" β†’ JVM heap full β†’ increase spark.executor.memory
# "Container killed by YARN for exceeding memory limits" β†’ overhead issue β†’ increase memoryOverhead
# "java.lang.OutOfMemoryError: GC overhead limit exceeded" β†’ too much GC β†’ memory pressure

# Step 3: Check for skew
# β†’ Spark UI β†’ Stages tab β†’ click on the slow stage β†’ sort tasks by duration
# If 1 task takes 45 min and 199 take 2 min β†’ data skew β†’ see Q27

# Step 4: Check GC time
# β†’ Spark UI β†’ Executors tab β†’ GC Time column
# If GC time > 10% of task time β†’ memory pressure β†’ increase memory or reduce data per task

Interview Tip: They will give you a scenario: "Your job ran for 6 hours and then failed with OOM. Walk me through debugging." Follow the 4-step checklist above.

What NOT to Say: "I would just double the executor memory." Blindly increasing memory without diagnosing the root cause is wasteful and often does not fix skew-related OOM.

Answer First: Tungsten is like organizing your desk for maximum efficiency β€” using a binary layout with no wasted space.

Memory Map: Tungsten's memory management? What is sun.misc.Unsafe -> unsafe binary rows avoid JVM object overhead -> off-heap pages reduce garbage collection -> generated operators process compact memory directly -> spill and CPU metrics expose the tradeoff [01_Spark_Architecture_and_Internals.md:615].

Q14: What is Tungsten's memory management? What is sun.misc.Unsafe?

Simple Explanation: Tungsten is like organizing your desk for maximum efficiency β€” using a binary layout with no wasted space.

Normally, Java stores objects with a lot of overhead: object headers, pointers, alignment padding. It is like storing each document in its own fancy folder with labels and dividers β€” wastes a lot of space.

Tungsten says: "Forget the fancy folders. Let me lay out all the data as raw bytes in a flat row on the desk." No wasted space, no folders to open and close. And since Tungsten manages memory directly (bypassing the JVM garbage collector), there are no "cleaning crew interruptions" (GC pauses).

sun.misc.Unsafe is the low-level Java API that gives Tungsten direct access to memory addresses β€” like having a master key to every shelf and drawer, bypassing all the normal Java safety locks.

Technical Answer: Tungsten manages memory outside the JVM garbage collector using sun.misc.Unsafe:

  • Direct memory allocation/deallocation (like C malloc/free)
  • Read/write raw bytes at memory addresses
  • No GC overhead for managed data

Tungsten stores data in a compact binary format: rows are serialized into byte arrays with:

  • Null bitmap
  • Fixed-length values (int, long, double)
  • Variable-length region (strings, arrays)
πŸ“ Architecture Diagram
# Tungsten binary row layout (conceptual):
# β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
# β”‚ Null bits β”‚ int(4B) β”‚ long(8B)β”‚ string (offset+len)β”‚
# β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
# No object headers, no pointers, no padding waste
# Everything packed tight β†’ better CPU cache utilization

Benefits: No GC pauses, better cache locality, explicit memory management, smaller memory footprint.

Interview Tip: When discussing Spark performance, mention Tungsten as the reason DataFrames are fast at the memory level (Catalyst optimizes the plan, Tungsten optimizes the execution).

What NOT to Say: "Tungsten only works with off-heap memory." Tungsten's binary format works with both on-heap and off-heap memory. Off-heap is optional.

SECTION 4: SHUFFLE DEEP DIVE

Answer First: A shuffle is like a postal sorting system. Imagine you have 100 post offices (map tasks) and each office has letters for 50 different cities (reduce partitions).

Memory Map: the complete shuffle process in Spark -> map tasks partition records into reducer buckets -> local files retain each bucket -> downstream tasks fetch and merge remote blocks -> exchange metrics expose bytes spill and skew [01_Spark_Architecture_and_Internals.md:656].

Q15: Explain the complete shuffle process in Spark.

Simple Explanation: A shuffle is like a postal sorting system. Imagine you have 100 post offices (map tasks) and each office has letters for 50 different cities (reduce partitions).

Map side (Shuffle Write): Each post office sorts all its letters by destination city, bundles them, and puts the bundles in outgoing mailboxes (writes to disk).

Reduce side (Shuffle Read): Each destination city sends a truck to ALL 100 post offices to collect its bundle. The truck drives around, picks up all the bundles, and brings them home for final delivery (processing).

This is why shuffle is expensive β€” every city needs to visit every post office. It is an all-to-all data exchange.

Technical Answer:

Map side (Shuffle Write):

  1. Each map task computes which reducer partition each record belongs to (hash or range partitioner)
  2. Records written to SortShuffleWriter (default since Spark 2.0)
  3. Data sorted by partition ID β†’ written to a single data file + index file per map task
  4. Number of shuffle files = number of map tasks (NOT map x reduce)

Reduce side (Shuffle Read):

  1. Each reduce task fetches its partition from ALL map tasks via BlockStoreShuffleReader
  2. Uses ShuffleClient (Netty) for remote block fetching
  3. Data deserialized and optionally sorted (for sort-merge operations)
python β€” editable
# Shuffle in action β€” this groupBy triggers a full shuffle
df = spark.read.parquet("s3://sales/")         # ← Stage 0: read data (no shuffle)
grouped = df.groupBy("region").sum("amount")   # ← Shuffle boundary: data redistributed by "region"
                                                # ← Stage 0 writes shuffle files (map side)
                                                # ← Stage 1 reads shuffle files (reduce side)
grouped.write.parquet("s3://output/")          # ← Stage 1: aggregate + write

# Check shuffle stats in Spark UI:
# β†’ Stages tab β†’ click stage β†’ "Shuffle Write" column shows bytes written (map side)
# β†’ Next stage β†’ "Shuffle Read" column shows bytes read (reduce side)
# If Shuffle Write >> Shuffle Read, you may have too many partitions

Why shuffle is expensive:

  • Disk I/O (write on map side, read on reduce side)
  • Network I/O (cross-executor data transfer)
  • Serialization/deserialization overhead
  • Can cause spill to disk if memory insufficient

Interview Tip: If asked "What is the most expensive operation in Spark?", the answer is always shuffle. Follow up with how to minimize it (filter early, broadcast joins, co-partitioning).

What NOT to Say: "Shuffle files = map tasks x reduce tasks." That was the old Hash Shuffle Manager. The modern Sort Shuffle Manager creates only one file per map task.

Answer First: This config tells Spark: "After a shuffle, split the data into this many pieces." The default is 200, which is almost never right.

Memory Map: spark.sql.shuffle.partitions and how do you tune it -> post-exchange data size determines reducer demand -> target bytes per task estimate a useful count -> available cores bound effective parallelism -> task duration and spill refine the setting [01_Spark_Architecture_and_Internals.md:708].

Q16: What is spark.sql.shuffle.partitions and how do you tune it?

Simple Explanation: This config tells Spark: "After a shuffle, split the data into this many pieces." The default is 200, which is almost never right.

Think of it like a pizza. If you cut a small pizza into 200 slices, each slice is uselessly tiny. If you cut a giant pizza into only 200 slices, each slice is too big to eat. You need the right number of slices for the pizza size.

Technical Answer:

  • Default: 200 (often too low for large data or too high for small data)
  • This controls the number of partitions after a shuffle (groupBy, join, repartition by column)

Tuning formula:

num_partitions = total_shuffle_data_size / target_partition_size
target_partition_size = 128 MB to 200 MB

Example: 50 GB shuffle data β†’ 50 GB / 200 MB = 250 partitions

python β€” editable
# Old approach (pre-AQE): manually set based on data size
spark.conf.set("spark.sql.shuffle.partitions", "250")  # ← 50 GB / 200 MB = 250

# Modern approach (with AQE): set HIGH and let AQE coalesce
spark.conf.set("spark.sql.shuffle.partitions", "2000")  # ← Intentionally over-provision
spark.conf.set("spark.sql.adaptive.enabled", "true")     # ← AQE merges tiny partitions automatically
# AQE will merge 2000 partitions down to the right number based on actual data size

With AQE: Set this high (e.g., 2000) and let coalescePartitions merge small partitions automatically. This is the modern best practice.

Common mistake: Setting it to 200 for both a 1 GB dataset and a 1 TB dataset.

Interview Tip: Always mention AQE as the modern approach. Manual tuning is a red flag that you are stuck in pre-Spark 3.0 thinking.

What NOT to Say: "I always use the default 200." That one answer tells the interviewer you have never tuned Spark at scale.

Answer First: If a cook goes home sick, all the prepped ingredients on their station are lost β€” the next stage needs to redo the prep.

Memory Map: the External Shuffle Service and why is it critical -> auxiliary service preserves map outputs beyond executor lifetime -> dynamic allocation can remove idle workers safely -> later reducers still fetch registered blocks -> lost-output errors reveal missing service support [01_Spark_Architecture_and_Internals.md:747].

Q17: What is the External Shuffle Service and why is it critical?

Simple Explanation: Imagine your line cooks (executors) prep ingredients (shuffle data) and leave them on their stations. If a cook goes home sick, all the prepped ingredients on their station are lost β€” the next stage needs to redo the prep.

The External Shuffle Service is like a shared pantry on each node. Cooks put their prepped ingredients in the pantry, not on their personal station. If a cook leaves, the ingredients are still in the pantry for anyone to use.

This is critical for dynamic allocation β€” Spark can scale executors up and down without losing shuffle data.

Technical Answer: A long-running auxiliary service on each worker node that serves shuffle files independently of executors.

Why critical:

  1. Dynamic allocation: Executors can be removed (scaled down) without losing their shuffle files. Without it, removing an executor means recomputing its shuffle output.
  2. Fault tolerance: If executor crashes, shuffle data still available
  3. Resource efficiency: Executors released between stages while shuffle output remains accessible

Config:

python β€” editable
spark.conf.set("spark.shuffle.service.enabled", "true")       # ← Enable the external shuffle service
spark.conf.set("spark.dynamicAllocation.enabled", "true")      # ← Enable dynamic executor scaling
# These two go together β€” dynamic allocation without ESS means shuffle data loss

Interview Tip: If asked about dynamic allocation, ALWAYS mention External Shuffle Service as a prerequisite. They are tightly coupled.

What NOT to Say: "Dynamic allocation just adds and removes executors automatically." You must mention that without ESS, removing executors loses shuffle data.

Answer First: FetchFailedException means: "A reduce task tried to pick up its package from another executor, but the package was not there." It is like a delivery truck arriving at a warehouse and finding it closed.

Memory Map: you handle the "Fetch Failed" exception -> reducer reports an unavailable map block -> scheduler invalidates the missing output -> upstream partition reruns on a healthy worker -> repeated failures point to network disk or executor loss [01_Spark_Architecture_and_Internals.md:777].

Q18: How do you handle the "Fetch Failed" exception?

Simple Explanation: FetchFailedException means: "A reduce task tried to pick up its package from another executor, but the package was not there." It is like a delivery truck arriving at a warehouse and finding it closed.

This is one of the most common production errors. The package (shuffle data) is missing because the executor that produced it either crashed, ran out of memory, or had a network issue.

Technical Answer: FetchFailedException = reduce task couldn't fetch shuffle data from a map task's executor.

Causes:

  • Executor OOM/crash during or after shuffle write
  • Network issues (timeout, connection refused)
  • Disk failures
  • Long GC pauses making executor unresponsive

Default behavior: Spark retries the entire stage (spark.stage.maxConsecutiveAttempts)

Fixes:

python β€” editable
# Fix 1: Enable External Shuffle Service (most important!)
spark.conf.set("spark.shuffle.service.enabled", "true")

# Fix 2: Increase executor memory to prevent OOM during shuffle write
spark.conf.set("spark.executor.memory", "16g")

# Fix 3: Increase shuffle fetch retry settings
spark.conf.set("spark.shuffle.io.maxRetries", "10")    # ← Default 3, increase for flaky networks
spark.conf.set("spark.shuffle.io.retryWait", "10s")    # ← Default 5s, increase for GC pauses

# Fix 4: Reduce shuffle data volume
# β†’ Filter and select columns BEFORE the shuffle operation
# β†’ Use broadcast joins to eliminate shuffles entirely

# Fix 5: Check for data skew (one executor overwhelmed β†’ OOM β†’ fetch failure)
# β†’ See Q27 for skew debugging

Interview Tip: This is a scenario question favorite: "Your job fails with FetchFailedException at 90% completion. What do you do?" Follow the 5 fixes above in order.

What NOT to Say: "Just retry the job." The retry will hit the same issue. You need to fix the root cause.

Answer First: Shuffle spill is like your desk overflowing during a big project. You run out of desk space (memory), so you start putting papers on the floor (disk).

Memory Map: shuffle spill. How do you detect and minimize it -> sort buffers exceed execution memory -> records flush to temporary disk runs -> merge work adds I/O and serialization -> memory and disk spill counters quantify pressure [01_Spark_Architecture_and_Internals.md:821].

Q19: Explain shuffle spill. How do you detect and minimize it?

Simple Explanation: Shuffle spill is like your desk overflowing during a big project. You run out of desk space (memory), so you start putting papers on the floor (disk). Working from the floor is much slower than working from your desk β€” you have to bend down, pick up papers, bring them back to the desk.

In Spark terms: when execution memory is full during a shuffle, data "spills" from memory to local disk. Disk is 10-100x slower than memory.

Technical Answer: Shuffle spill occurs when execution memory is exhausted during shuffle operations. Data spills from memory to disk.

Detect in Spark UI:

  • Stage detail page β†’ Spill (Memory) and Spill (Disk) columns
  • Spill (Memory) > 0 indicates memory pressure

Minimize:

# 1. Give executors more memory
spark.conf.set("spark.executor.memory", "16g")
# 2. Increase Spark's share of the heap
spark.conf.set("spark.memory.fraction", "0.7") # ← Default 0.6, increase to 0.7
# 3. Increase parallelism (smaller data per task = less memory needed per task)
spark.conf.set("spark.sql.shuffle.partitions", "1000") # ← More partitions = smaller chunks
# 4. Filter early and select only needed columns BEFORE the shuffle
df = df.filter(df.year == 2024) \ # ← Reduce data volume before shuffle
.select("region", "amount") \ # ← Drop unnecessary columns
.groupBy("region").sum("amount") # ← Now the shuffle is much smaller
# 5. Use mapPartitions instead of map (reduces per-row object overhead)
# 6. Consider off-heap memory (avoids GC pressure on the heap)
spark.conf.set("spark.memory.offHeap.enabled", "true")
spark.conf.set("spark.memory.offHeap.size", "4g")

Interview Tip: When looking at Spark UI, always check Spill columns. If Spill (Disk) is large, you have a memory problem that is silently slowing your job.

What NOT to Say: "Spill is normal and acceptable." Small spills are OK, but large spills (GBs) indicate a serious performance problem. A job with heavy spill can be 10x slower.

SECTION 5: SERIALIZATION

Answer First: Serialization is how Spark "packs" data for shipping between executors. Think of it like packing for a move.

Memory Map: all Spark serialization options with trade-offs -> record shape selects an encoding -> encoding changes payload size and CPU -> transfer crosses process or network boundaries -> task duration exposes the trade-off [01_Spark_Architecture_and_Internals.md:865].

Q20: Compare all Spark serialization options with trade-offs.

Simple Explanation: Serialization is how Spark "packs" data for shipping between executors. Think of it like packing for a move:

  • Java serialization = throwing everything into garbage bags. Works for anything, but bulky and slow to unpack.
  • Kryo = using labeled moving boxes. 10x faster, more compact, but you need to label each box type (register classes).
  • Tungsten binary = vacuum-sealing everything flat. Most compact, fastest. Used automatically by DataFrames.
  • Apache Arrow = using standardized shipping containers. Great for cross-system transfer (PySpark to Pandas).

Technical Answer:

SerializationSpeedSizeUse Case
Java (default for RDD)SlowLargeAny Serializable class. Avoid for performance.
Kryo10x fasterCompactRDD operations. Must register classes.
Tungsten binaryFastestMost compactDataFrames/Datasets internally. Not configurable. Off-heap, no GC.
Apache ArrowVery fastColumnarPySpark ↔ Pandas conversion. Zero-copy. Enable with spark.sql.execution.arrow.pyspark.enabled=true

Kryo config:

python β€” editable
spark.conf.set("spark.serializer", "org.apache.spark.serializer.KryoSerializer")  # ← Use Kryo instead of Java
spark.conf.set("spark.kryo.registrationRequired", "true")   # ← Force class registration (best perf)
# Register your custom classes:
# conf.registerKryoClasses(Array(classOf[MyClass]))          # ← Scala syntax
python β€” editable
# Arrow for PySpark ↔ Pandas (huge speedup for toPandas/createDataFrame)
spark.conf.set("spark.sql.execution.arrow.pyspark.enabled", "true")  # ← Enable Arrow
pdf = df.toPandas()  # ← With Arrow: zero-copy columnar transfer (fast!)
                      # ← Without Arrow: row-by-row pickle serialization (slow!)

Key interview point: DataFrames are always faster than RDDs because they use Tungsten binary format internally, bypassing Java/Kryo serialization entirely.

Interview Tip: If asked "How do you speed up PySpark toPandas()?", the answer is Arrow. If asked "How do you speed up RDD operations?", the answer is Kryo.

What NOT to Say: "I use Java serialization because it is the default." Java serialization is the slowest option. Always switch to Kryo for RDDs.

Answer First: PySpark is like a bilingual translator between Python and Java. Your Python code does not run directly on the cluster. Instead.

Memory Map: PySpark actually execute Python code? Explain the Py4J gateway architecture -> pyspark actually execute python code py4j gateway architecture defines how data crosses the Python and JVM boundary -> API call builds a JVM plan -> Python callback crosses a process boundary -> rows or Arrow batches execute externally -> worker time quantifies the penalty [01_Spark_Architecture_and_Internals.md:905].

Q21: How does PySpark actually execute Python code? Explain the Py4J gateway architecture.

Simple Explanation: PySpark is like a bilingual translator between Python and Java. Your Python code does not run directly on the cluster. Instead:

  1. You write Python code.
  2. A translator (Py4J) converts your Python calls into Java calls on the Driver.
  3. The Java Driver sends work to Java Executors β€” no Python on the executors for native DataFrame operations.
  4. BUT if you use a Python UDF, each executor has to spawn a Python worker process, ship data to it via a socket, wait for it to process, and get results back. This Python worker is the bottleneck.

Think of it like a meeting with an interpreter. If everyone speaks the same language (Java/DataFrame ops), communication is instant. But if someone insists on speaking a different language (Python UDF), everything has to be translated back and forth β€” slow.

Technical Answer:

πŸ“ Architecture Diagram
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”      β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   Python Driver     β”‚      β”‚    JVM Driver        β”‚
β”‚                     β”‚ Py4J β”‚                      β”‚
β”‚  PySpark API calls ─┼──────┼→ Java SparkContext   β”‚
β”‚                     β”‚      β”‚                      β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜      β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                        β”‚
                              β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                              β”‚    Executors (JVM)  β”‚
                              β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”β”‚
                              β”‚  β”‚ Python Worker    β”‚β”‚ ← Spawned for UDFs
                              β”‚  β”‚ (subprocess)     β”‚β”‚
                              β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜β”‚
                              β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

How it works:

  1. Python SparkSession wraps a Java SparkSession via Py4J gateway
  2. DataFrame operations are translated to JVM calls β†’ no Python on executors for native operations
  3. When a Python UDF is used:
    • Each executor spawns a Python worker process
    • Data serialized (pickle or Arrow) β†’ sent via socket to Python worker
    • Python worker processes data β†’ sends results back
    • This JVM↔Python boundary is the main performance overhead of PySpark vs Scala Spark
python β€” editable
# FAST: Pure DataFrame operations β€” runs entirely in JVM, no Python on executors
df.filter(df.amount > 100).groupBy("region").sum("amount")
# ← Py4J translates this to Java calls on the Driver
# ← Executors run pure JVM code β€” same speed as Scala!

# SLOW: Python UDF β€” spawns Python workers on every executor
from pyspark.sql.functions import udf
@udf("double")
def add_tax(amount):                       # ← This Python function must run on executors
    return amount * 1.1
df.withColumn("total", add_tax(df.amount)) # ← Data serialized β†’ shipped to Python β†’ result shipped back
# ← 2-10x slower than the native DataFrame equivalent!

# BETTER: Pandas UDF (Arrow-based) β€” vectorized, much faster than row-at-a-time UDF
from pyspark.sql.functions import pandas_udf
@pandas_udf("double")
def add_tax_fast(amount: pd.Series) -> pd.Series:  # ← Processes batches via Arrow
    return amount * 1.1
df.withColumn("total", add_tax_fast(df.amount))     # ← Arrow zero-copy transfer, vectorized
# ← 10-100x faster than regular Python UDFs!

Key insight: Pure DataFrame/SQL operations in PySpark are just as fast as Scala because they run entirely in the JVM. The overhead only appears with Python UDFs.

Interview Tip: If asked "Is PySpark slower than Scala Spark?", the nuanced answer is: "For DataFrame/SQL operations, they are identical. The difference appears only with Python UDFs. Use Pandas UDFs to minimize the gap."

What NOT to Say: "PySpark is always slower than Scala." This is a common misconception. For native operations (99% of pipeline code), there is zero performance difference.

SECTION 6: FAULT TOLERANCE & SPECULATION

Answer First: Spark's fault tolerance is based on a simple but powerful idea: remember how to redo the work, not the work itself.

Memory Map: Spark achieve fault tolerance -> spark achieve fault tolerance selects lineage replay or speculative retry semantics -> lost or slow task triggers recovery -> lineage replay or duplicate attempt recomputes output -> idempotent winner is retained -> retry metrics confirm recovery [01_Spark_Architecture_and_Internals.md:977].

Q22: How does Spark achieve fault tolerance?

Simple Explanation: Spark's fault tolerance is based on a simple but powerful idea: remember how to redo the work, not the work itself.

Instead of replicating data across nodes (like HDFS does), Spark keeps a "recipe" (lineage) for every piece of data. If data is lost (executor crashes), Spark reruns the recipe to recreate it. This is like a chef who does not save every dish in a freezer, but instead keeps the recipe book β€” if a dish is dropped, they just cook it again.

Technical Answer:

  • RDD lineage: Each RDD knows how to reconstruct itself from its parent. If a partition is lost, Spark replays the transformations to recreate it.
  • Checkpointing: Breaks lineage by saving data to reliable storage. Two types:
    • Reliable checkpoint: Saves to HDFS/S3 (fault-tolerant)
    • Local checkpoint: Saves to executor local storage (not fault-tolerant, faster)
  • Stage retry: If a fetch failure occurs, the entire stage is recomputed
  • Task retry: Individual tasks are retried on failure (default: 4 attempts, spark.task.maxFailures)
  • Structured Streaming: Checkpoints track offsets + state for exactly-once recovery
python β€” editable
# Checkpointing example β€” break a long lineage
spark.sparkContext.setCheckpointDir("s3://checkpoints/")  # ← Set checkpoint storage location

df = spark.read.parquet("s3://data/")
for i in range(100):                          # ← 100 iterations builds a DEEP lineage
    df = df.withColumn("score", some_transform(df.score))
    if i % 10 == 0:                           # ← Every 10 iterations, checkpoint
        df = df.checkpoint()                  # ← Saves to S3, truncates lineage
        df.count()                            # ← Force materialization (checkpoint is lazy!)
# Without checkpointing: iteration 100 has a lineage of 100 transformations
# With checkpointing: lineage is at most 10 transformations deep

Interview Tip: If asked "How does Spark handle failures?", start with lineage (the core idea), then mention checkpointing as the optimization for long lineages.

What NOT to Say: "Spark replicates data like HDFS for fault tolerance." No β€” Spark uses lineage (re-computation), not replication. That is the fundamental design difference.

Answer First: Whichever delivers first, you keep that order and cancel the other. You pay a little extra (resources), but you are guaranteed to get your food fast.

Memory Map: speculative execution? When should you enable vs disable it -> scheduler detects an abnormally slow attempt -> duplicate work launches on another worker -> first successful result wins -> skew and side effects can make duplication harmful [01_Spark_Architecture_and_Internals.md:1015].

Q23: What is speculative execution? When should you enable vs disable it?

Simple Explanation: Imagine you order food from two delivery services simultaneously. Whichever delivers first, you keep that order and cancel the other. You pay a little extra (resources), but you are guaranteed to get your food fast.

Speculative execution does the same thing: if one task is running much slower than others (a "straggler"), Spark launches a copy of that task on another executor. Whichever copy finishes first wins. The straggler is killed.

Technical Answer: Spark re-launches slow tasks ("stragglers") on other executors and takes the result from whichever finishes first.

Config:

python β€” editable
spark.conf.set("spark.speculation", "true")              # ← Enable speculation
spark.conf.set("spark.speculation.multiplier", "3")      # ← Task must be 3x slower than median
spark.conf.set("spark.speculation.quantile", "0.75")     # ← 75% of tasks must complete before speculation starts

Enable when:

  • Tasks have variable duration due to hardware issues or data locality
  • You're running on heterogeneous hardware
  • Network storage has variable latency

Disable when:

  • Tasks have side effects (non-idempotent writes) β€” speculation would write duplicates
  • Slowness is due to data skew β€” the re-launched task processes the same skewed partition
  • You're already using most of cluster resources

Interview Tip: Always pair this with the skew caveat. "Speculation does NOT fix skew β€” if the task is slow because of a huge partition, the duplicate task will be equally slow."

What NOT to Say: "Speculation fixes slow tasks." It fixes stragglers caused by hardware/network issues, NOT tasks that are slow due to data skew.

Answer First: Broadcast variable = The Head Chef posts the daily specials menu on the kitchen wall. Every cook can read it, but nobody can change it.

Memory Map: Accumulators and Broadcast variables? What are the pitfalls -> driver publishes each broadcast value -> executor caches a read-only copy -> accumulator updates may repeat during task retry -> driver observes merged diagnostic values [01_Spark_Architecture_and_Internals.md:1048].

Q24: What are Accumulators and Broadcast variables? What are the pitfalls?

Simple Explanation: In the restaurant kitchen:

Broadcast variable = The Head Chef posts the daily specials menu on the kitchen wall. Every cook can read it, but nobody can change it. It is a read-only shared reference. Instead of giving every cook their own copy (shipping with every task), one copy is posted per kitchen (executor).

Accumulator = A click counter at the door. Every time a customer walks in, the counter goes up. Cooks can add to it, but only the Head Chef can read the total. It is a write-only (from executor perspective) shared counter.

Technical Answer:

Broadcast variables:

  • Read-only variables cached on each executor (not shipped with every task)
  • Use for large lookup tables
python β€” editable
lookup = spark.sparkContext.broadcast(large_dict)  # ← Ship once to each executor (not per task!)
df.filter(col("key").isin(lookup.value.keys()))    # ← Read the broadcasted dict on executors
# Memory used = dict_size Γ— num_executors (one copy per executor)
  • Pitfalls:
    • If too large β†’ OOM on driver (collects before broadcasting)
    • Memory used = table_size x num_executors
    • Must .unpersist() or .destroy() manually

Accumulators:

  • Write-only variables "added" to by executors, readable only by driver
python β€” editable
counter = spark.sparkContext.accumulator(0)     # ← Initialize counter on driver
rdd.foreach(lambda x: counter.add(1))           # ← Each executor adds to counter
print(counter.value)                             # ← Only the driver reads the final value
  • Critical pitfall: In transformations (not actions), accumulators may be incremented more than once if tasks are retried or stages re-executed. Only use accumulators inside actions for guaranteed exactly-once semantics.
python β€” editable
# DANGEROUS β€” accumulator in a transformation (lazy, may re-execute!)
counter = spark.sparkContext.accumulator(0)
rdd2 = rdd.map(lambda x: (counter.add(1), x)[1])  # ← BAD! If task retries, counter increments again
rdd2.count()                                         # ← Counter value may be WRONG

# SAFE β€” accumulator in an action (eager, runs exactly once per task)
counter = spark.sparkContext.accumulator(0)
rdd.foreach(lambda x: counter.add(1))               # ← GOOD! foreach is an action
print(counter.value)                                 # ← Counter value is reliable

Interview Tip: The accumulator pitfall is a classic gotcha question. Always mention: "Accumulators are only guaranteed accurate inside actions, not transformations."

What NOT to Say: "Accumulators are like global variables you can read and write from anywhere." They are write-only from executors and only accurately reflect counts when used inside actions.

SECTION 7: SCENARIO-BASED ARCHITECTURE QUESTIONS

Answer First: The host (Task Scheduler) seats the first 100 customers. As each table finishes (task completes), the next customer in line is seated.

Memory Map: Scenario β€” Your Spark job has 1000 tasks but only 100 executor cores. How does Spark schedule them -> available cores admit one task per configured CPU share -> surplus tasks wait in scheduler queues -> completed waves release slots for successors -> timeline confirms ten scheduling waves [01_Spark_Architecture_and_Internals.md:1101].

Q25: Scenario β€” Your Spark job has 1000 tasks but only 100 executor cores. How does Spark schedule them?

Simple Explanation: Think of a restaurant with 100 tables but 1000 customers waiting. You cannot seat everyone at once. The host (Task Scheduler) seats the first 100 customers. As each table finishes (task completes), the next customer in line is seated. With 1000 customers and 100 tables, you need roughly 10 waves to serve everyone.

Spark adds a locality preference: it tries to seat each customer at the table closest to the kitchen (data). If their preferred table is not available within 3 seconds (spark.locality.wait), they get seated anywhere.

Technical Answer:

  • Spark doesn't run all 1000 tasks simultaneously
  • The Task Scheduler maintains a queue of pending tasks
  • It assigns tasks to available slots (one slot = one core) respecting data locality
  • When a task completes, the next pending task is scheduled on the freed slot
  • If spark.locality.wait (default 3s) expires and no local slot is available, the task is scheduled on a less-local slot
  • Throughput: ~100 tasks running at any time, with 10 waves of tasks

Interview Tip: Follow up with: "If all 10 waves take similar time, your parallelism is good. If the last wave has only 5 tasks while 95 cores sit idle, you have a tail problem β€” increase partitions."

What NOT to Say: "Spark waits until all 1000 tasks are ready before starting." No β€” Spark starts immediately with whatever cores are available and processes in waves.

Answer First: This is like using a forklift to move a shoebox. The 50 MB table is tiny β€” it should be broadcast (copied) to every executor so the join happens locally without any shuffle. But Spark is using a Sort-Merge Join (the forklift), which shuffles both tables across the network.

Memory Map: Scenario β€” You're running a join between a 500 GB and a 50 MB table. The job is doing a Sort-Merge Join. What's wrong -> re running join between 500 gb and 50 mb table job doing sort merge join s determines the join operator and required data movement -> input sizes and key distribution shape the join -> broadcast or exchange moves data -> physical operator combines rows -> shuffle and skew metrics validate selection [01_Spark_Architecture_and_Internals.md:1122].

Q26: Scenario β€” You're running a join between a 500 GB and a 50 MB table. The job is doing a Sort-Merge Join. What's wrong?

Simple Explanation: This is like using a forklift to move a shoebox. The 50 MB table is tiny β€” it should be broadcast (copied) to every executor so the join happens locally without any shuffle. But Spark is using a Sort-Merge Join (the forklift), which shuffles both tables across the network.

The GPS (Catalyst) chose the wrong route because it does not know the table is only 50 MB. You need to either tell Catalyst (collect statistics) or force the route (broadcast hint).

Technical Answer: The 50 MB table should trigger a Broadcast Hash Join (default threshold is 10 MB), but it's not happening. Possible reasons:

  1. Statistics are wrong/missing: Spark doesn't know the table is only 50 MB

    • Fix: ANALYZE TABLE small_table COMPUTE STATISTICS
    • Or: Set spark.sql.autoBroadcastJoinThreshold=52428800 (50 MB)
  2. The 50 MB is the size AFTER filters but Spark uses pre-filter size for planning

    • Fix: AQE will detect this at runtime and switch to broadcast
  3. Column statistics missing: Without CBO, Spark uses file size

    • Fix: Force broadcast: df1.join(broadcast(df2), "key")
python β€” editable
# Fix 1: Collect statistics so Catalyst knows the true size
spark.sql("ANALYZE TABLE small_table COMPUTE STATISTICS")  # ← Now Catalyst knows it's 50 MB

# Fix 2: Increase broadcast threshold
spark.conf.set("spark.sql.autoBroadcastJoinThreshold", "52428800")  # ← 50 MB in bytes

# Fix 3: Force broadcast with a hint (most reliable)
from pyspark.sql.functions import broadcast
result = big_df.join(broadcast(small_df), "key")  # ← Forces broadcast join regardless of stats
# ← small_df is sent to every executor (50 MB Γ— N executors)
# ← No shuffle needed for big_df β€” processed locally!

# Fix 4: Let AQE handle it (if filters reduce the table size at runtime)
spark.conf.set("spark.sql.adaptive.enabled", "true")  # ← AQE detects small table mid-execution

Interview Tip: This is one of the most common scenario questions. Walk through all three causes systematically β€” it shows deep understanding.

What NOT to Say: "Just increase the broadcast threshold to 10 GB." Broadcasting a truly large table will OOM your driver and executors. Only broadcast small tables.

Answer First: 199 workers each have 100 items to pack. But one worker got stuck with 100,000 items β€” that is data skew. No matter how fast that worker is, they are drowning in work while everyone else is done and idle.

Memory Map: Scenario β€” Your job has 200 tasks, 199 finish in 2 minutes, but 1 task takes 45 minutes. What's happening -> task-duration outlier signals skew or unhealthy hardware -> input and shuffle metrics separate causes -> repartitioning or executor replacement removes bottleneck -> repeated stage timing verifies balanced completion [01_Spark_Architecture_and_Internals.md:1165].

Q27: Scenario β€” Your job has 200 tasks, 199 finish in 2 minutes, but 1 task takes 45 minutes. What's happening?

Simple Explanation: Imagine 200 workers packing boxes. 199 workers each have 100 items to pack. But one worker got stuck with 100,000 items β€” that is data skew. No matter how fast that worker is, they are drowning in work while everyone else is done and idle.

The solution: either split that worker's pile among multiple workers (AQE skew join, salting), or find a way to reduce the pile before it gets assigned (pre-filtering, broadcast join).

Technical Answer: This is classic data skew β€” one partition has disproportionately more data.

How to confirm:

  • Spark UI β†’ Stage detail β†’ sort tasks by duration
  • Check Input Size/Records for the slow task vs others
  • Check Shuffle Read Size for the slow task

Fix options (in order of preference):

python β€” editable
# Fix 1: Enable AQE skew join optimization (easiest, automatic)
spark.conf.set("spark.sql.adaptive.skewJoin.enabled", "true")
# ← AQE detects the skewed partition at runtime
# ← Splits it into smaller sub-partitions
# ← Replicates the corresponding partition from the other join side

# Fix 2: Salting β€” add random prefix to break up the hot key
from pyspark.sql.functions import concat, lit, rand, floor
salt_buckets = 10
# Add salt to the skewed table
skewed_df = skewed_df.withColumn("salted_key",
    concat(col("key"), lit("_"), floor(rand() * salt_buckets)))  # ← key β†’ key_0, key_1, ... key_9
# Explode the other table to match all salt values
other_df = other_df.crossJoin(
    spark.range(salt_buckets).withColumnRenamed("id", "salt"))
other_df = other_df.withColumn("salted_key",
    concat(col("key"), lit("_"), col("salt")))                   # ← Replicate rows for each salt
# Now join on salted_key β€” the hot key is split across 10 partitions!
result = skewed_df.join(other_df, "salted_key")

# Fix 3: Broadcast join β€” if the other table fits in memory
result = skewed_df.join(broadcast(small_df), "key")              # ← No shuffle = no skew problem

# Fix 4: Isolate-and-union β€” process skewed keys separately
hot_keys = ["key_X", "key_Y"]                                   # ← Known skewed keys
skewed_part = df.filter(col("key").isin(hot_keys))               # ← Handle separately
normal_part = df.filter(~col("key").isin(hot_keys))              # ← Handle normally
result = normal_part.join(other_df, "key").union(
    skewed_part.join(broadcast(other_df), "key"))                # ← Broadcast for skewed keys only

Interview Tip: Data skew is the #1 performance problem in Spark at scale. Be ready to explain salting step-by-step on a whiteboard.

What NOT to Say: "Just add more executors." More executors do not fix skew β€” the bottleneck is one partition, not total cluster capacity.

Answer First: HDFS is like having filing cabinets in your office β€” you open a drawer and grab the file instantly. S3 is like a storage locker across town β€” you have to drive there, wait in line, request your item, and drive back. Every single file access has this overhead.

Memory Map: Scenario β€” Your Spark job reads from S3 and is 3x slower than reading from HDFS. Why -> remote object storage adds request latency -> connector settings control parallel reads -> file sizing determines request count -> throughput and request metrics validate tuning [01_Spark_Architecture_and_Internals.md:1219].

Q28: Scenario β€” Your Spark job reads from S3 and is 3x slower than reading from HDFS. Why?

Simple Explanation: HDFS is like having filing cabinets in your office β€” you open a drawer and grab the file instantly. S3 is like a storage locker across town β€” you have to drive there, wait in line, request your item, and drive back. Every single file access has this overhead.

The key differences:

  • No data locality: With HDFS, Spark runs tasks where the data lives (reading your own notes = PROCESS_LOCAL). With S3, all reads are remote (calling another office = ANY).
  • Slow listing: Finding out what files exist in an S3 "folder" requires multiple API calls. In HDFS, the NameNode knows instantly.
  • High per-request latency: Each S3 GET = 50-100 ms. HDFS read = ~1 ms.

Technical Answer: S3 is an object store, not a filesystem. Key differences:

  1. List operations are slow: S3 list is O(n) and requires multiple API calls. HDFS metadata is in-memory on NameNode.
  2. No data locality: With HDFS, tasks run on the node where data resides (PROCESS_LOCAL). With S3, all reads are remote (ANY).
  3. High latency per request: Each S3 GET has ~50-100 ms latency vs ~1 ms for HDFS
  4. No append: S3 doesn't support append; each write creates a new object
  5. Eventual consistency (historically): Though S3 is now strongly consistent for PUTs

Optimizations for S3:

python β€” editable
spark.conf.set("spark.hadoop.fs.s3a.connection.maximum", "200")       # ← More parallel connections to S3
spark.conf.set("spark.sql.files.maxPartitionBytes", "268435456")       # ← 256 MB partitions (larger = fewer S3 calls)
spark.conf.set("spark.sql.files.openCostInBytes", "0")                 # ← Combine small files aggressively
# With openCostInBytes=0, Spark packs many small files into one partition
# instead of treating each file as 4 MB (the default open cost)
  • Use Delta Lake (reduces list operations via transaction log)
  • Use Auto Loader (file notification mode avoids listing)

Interview Tip: If asked "How do you optimize Spark on S3?", mention: larger partition sizes, more connections, Delta Lake for metadata, and Auto Loader for ingestion.

What NOT to Say: "S3 and HDFS perform the same because they both store files." They have fundamentally different access patterns and latency characteristics.

Answer First: When Spark tries to re-read the recipe (plan the DAG), it has to flip through all 100 pages.

Memory Map: Scenario β€” You have an iterative ML algorithm that runs 100 iterations. After iteration 50, the job gets extremely slow and eventually fails with StackOverflow. Why -> iterative lineage grows with every derived dataset -> scheduler traversal eventually causes call-stack exhaustion -> checkpoint truncates dependency history -> later iterations reuse bounded lineage [01_Spark_Architecture_and_Internals.md:1255].

Q29: Scenario β€” You have an iterative ML algorithm that runs 100 iterations. After iteration 50, the job gets extremely slow and eventually fails with StackOverflow. Why?

Simple Explanation: Think of it like a recipe that says "take the result of step 1, apply step 2, take the result, apply step 3..." After 100 steps, the recipe is 100 pages long. When Spark tries to re-read the recipe (plan the DAG), it has to flip through all 100 pages. Eventually, the recipe book is so thick that Spark's stack overflows trying to hold all the pages open at once.

The fix: every 10 steps, take a photo of the current dish (checkpoint). Now the recipe starts from the photo, not from scratch. The recipe never gets longer than 10 steps.

Technical Answer: The lineage graph is growing with each iteration. After 100 iterations, the DAG has 100 levels of dependencies. When Spark tries to compute the plan or recover a partition, it traverses this deep lineage β†’ StackOverflowError.

Fix: Checkpoint every N iterations:

python β€” editable
spark.sparkContext.setCheckpointDir("/checkpoint/path")  # ← Set checkpoint location (HDFS/S3)

for i in range(100):
    rdd = rdd.map(transform_function)           # ← Each iteration adds one level to the lineage
    if i % 10 == 0:                             # ← Every 10 iterations...
        rdd.checkpoint()                        # ← Mark for checkpointing (truncates lineage)
        rdd.count()                             # ← Force materialization (checkpoint is lazy!)
# Without checkpoint: lineage depth = 100 β†’ StackOverflow
# With checkpoint every 10: lineage depth = max 10 β†’ stable

Or for DataFrames:

python β€” editable
if i % 10 == 0:
    df = df.checkpoint()  # ← Truncates lineage, saves to reliable storage

Interview Tip: This is a classic ML/graph algorithm question. Always mention checkpointing as the solution to growing lineage.

What NOT to Say: "Increase the JVM stack size." That is a band-aid. The lineage will keep growing and eventually overflow any stack size. Checkpointing is the real fix.

Answer First: These two configs control how Spark "slices" files into partitions when reading.

Memory Map: spark.sql.files.maxPartitionBytes and spark.sql.files.openCostInBytes -> maximum file size caps bytes assigned per partition -> open cost estimates per-file scheduling overhead -> Spark packs files into input partitions -> task-size metrics verify balanced reads [01_Spark_Architecture_and_Internals.md:1289].

Q30: What is spark.sql.files.maxPartitionBytes and spark.sql.files.openCostInBytes?

Simple Explanation: These two configs control how Spark "slices" files into partitions when reading.

  • maxPartitionBytes = maximum slice size. Like cutting a pizza β€” each slice cannot be bigger than this (default 128 MB).
  • openCostInBytes = assumed overhead of opening each file. Spark pretends each file is at least this big (default 4 MB) when deciding how to combine files.

The second one is tricky: if you have 10,000 tiny 1 KB files, Spark treats each as 4 MB β†’ 10,000 partitions (too many!). Set it to 0, and Spark combines them aggressively into fewer, larger partitions.

Technical Answer:

  • maxPartitionBytes (default 128 MB): Maximum size of a partition when reading files. Spark splits large files into partitions of this size.
  • openCostInBytes (default 4 MB): Estimated cost of opening a file. Used to decide when to combine small files into one partition. If you have many tiny files, reducing this to 0 forces more aggressive file combining.

Example: 10,000 files of 1 KB each:

python β€” editable
# With default openCostInBytes=4 MB:
# Each 1 KB file is treated as 4 MB β†’ 10,000 "virtual" files β†’ 10,000 partitions (way too many!)
# β†’ Massive task scheduling overhead, tiny tasks

# Fix: set openCostInBytes=0
spark.conf.set("spark.sql.files.openCostInBytes", "0")       # ← Treat files at their actual size
spark.conf.set("spark.sql.files.maxPartitionBytes", "268435456")  # ← 256 MB partitions
# Now: 10,000 Γ— 1 KB = 10 MB total β†’ fits in 1 partition (or a few)
# β†’ Fast, efficient, no scheduling overhead

Interview Tip: This is the go-to answer for "small files problem in Spark." Combine with Delta Lake's OPTIMIZE command for a complete answer.

What NOT to Say: "I repartition after reading to fix the small files problem." That adds a shuffle. Tuning openCostInBytes fixes it at the read stage β€” no shuffle needed.

SECTION 8: QUICK-FIRE QUESTIONS (Common in Phone Screens)

Answer First: Simple Explanation: Think of transformations as writing a recipe (lazy β€” nothing happens yet) and actions as turning on the stove (triggers actual cooking). You can write as many recipe steps as you want, but the kitchen stays cold until you hit "cook.".

Memory Map: Transformation vs Action -> transformation vs action clarifies its effect on the lazy execution graph -> transformation extends a lazy graph -> Catalyst sees relational expressions -> action triggers execution -> stages and tasks materialize the result [01_Spark_Architecture_and_Internals.md:1324].

Q31: Transformation vs Action?

Simple Explanation: Think of transformations as writing a recipe (lazy β€” nothing happens yet) and actions as turning on the stove (triggers actual cooking). You can write as many recipe steps as you want, but the kitchen stays cold until you hit "cook."

Technical Answer: Transformations are lazy (return new RDD/DF, not computed until action). Actions trigger computation (return value or write to storage). Examples: map/filter = transformation; collect/count/write = action.

Interview Tip: They may follow up with "Why is laziness beneficial?" β€” answer: it lets Catalyst optimize the full plan globally.

What NOT to Say: "Transformations execute immediately but return a new DataFrame." No β€” they are lazy and execute ONLY when an action is called.

Answer First: Simple Explanation: Imagine counting votes by state. reduceByKey = each county counts its own votes first, then sends totals to the state office (map-side combine = less data shipped). groupByKey = every county sends ALL individual ballots to the state office, which does all the counting (no pre-aggregation = way more data shipped).

Memory Map: reduceByKey vs groupByKey -> map-side aggregation combines values before exchange -> raw grouping transfers every value -> reduced network volume usually favors the combiner path -> record and byte counts prove the difference [01_Spark_Architecture_and_Internals.md:1334].

Q32: reduceByKey vs groupByKey?

Simple Explanation: Imagine counting votes by state. reduceByKey = each county counts its own votes first, then sends totals to the state office (map-side combine = less data shipped). groupByKey = every county sends ALL individual ballots to the state office, which does all the counting (no pre-aggregation = way more data shipped).

Technical Answer: reduceByKey does a map-side combine (local aggregation before shuffle). groupByKey shuffles ALL data first, then aggregates. reduceByKey always preferred β€” less data transferred.

Interview Tip: If asked "When would you ever use groupByKey?", the answer is: "Almost never. The only case is when you need ALL values for a key (not an aggregate), and even then, consider combineByKey."

What NOT to Say: "They do the same thing." The shuffle cost difference can be 10x or more.

Answer First: Simple Explanation: RDD = driving with a paper map (you control everything, no optimization). DataFrame = using GPS navigation (Catalyst optimizes the route). Dataset = GPS with voice commands in your native language (typed API, Scala/Java only).

Memory Map: DataFrame vs Dataset vs RDD -> RDD exposes low-level immutable records -> typed Dataset adds encoder-backed JVM types -> DataFrame exposes relational rows to Catalyst -> language and optimizer needs choose the abstraction [01_Spark_Architecture_and_Internals.md:1344].

Q33: DataFrame vs Dataset vs RDD?

Simple Explanation: RDD = driving with a paper map (you control everything, no optimization). DataFrame = using GPS navigation (Catalyst optimizes the route). Dataset = GPS with voice commands in your native language (typed API, Scala/Java only). In PySpark, you only have DataFrame (which is Dataset[Row] under the hood).

Technical Answer: RDD = low-level, no optimization. DataFrame = distributed table, Catalyst-optimized, schema-aware. Dataset = typed DataFrame (Scala/Java only). In PySpark, DataFrame = Dataset[Row]. Always prefer DataFrame over RDD.

Interview Tip: If asked "When would you use RDD?", valid answers: (1) low-level control over partitioning, (2) unstructured data that does not fit a schema, (3) legacy code.

What NOT to Say: "RDDs are faster because they have less overhead." The opposite is true β€” DataFrames are faster because of Catalyst and Tungsten.

Answer First: Simple Explanation: Imagine a GPS that plans the entire route BEFORE you start driving, vs one that gives you directions one turn at a time. The full-route GPS can find shortcuts and avoid traffic.

Memory Map: lazy evaluation -> transformations record lineage without running tasks -> optimizer sees the complete relational graph -> an action creates executable stages -> unused work can be pruned before scheduling [01_Spark_Architecture_and_Internals.md:1354].

Q34: Why lazy evaluation?

Simple Explanation: Imagine a GPS that plans the entire route BEFORE you start driving, vs one that gives you directions one turn at a time. The full-route GPS can find shortcuts and avoid traffic. That is what lazy evaluation gives Catalyst β€” a complete view of all operations so it can optimize globally.

Technical Answer: Enables Catalyst to see the full plan before executing β†’ allows global optimizations (predicate pushdown, join reordering, column pruning). Without lazy evaluation, each operation would execute independently.

Interview Tip: Connect this to Catalyst. Laziness is not just about deferring work β€” it is about enabling optimization.

What NOT to Say: "Lazy evaluation just delays computation to save resources." The primary benefit is optimization, not resource savings.

Answer First: Simple Explanation: A new stage starts whenever cars need to merge and switch lanes (a shuffle). Within a stage, all cars stay in their lane (narrow transformations, pipelined). The shuffle is the toll plaza that separates stages.

Memory Map: What triggers a new stage -> a wide dependency introduces an exchange boundary -> upstream work must finish before downstream fetches -> scheduler cuts the graph at that boundary -> UI stage edges confirm the split [01_Spark_Architecture_and_Internals.md:1364].

Q35: What triggers a new stage?

Simple Explanation: A new stage starts whenever cars need to merge and switch lanes (a shuffle). Within a stage, all cars stay in their lane (narrow transformations, pipelined). The shuffle is the toll plaza that separates stages.

Technical Answer: A wide dependency (shuffle). Each shuffle boundary creates a new stage. All narrow transformations are pipelined within a single stage.

Interview Tip: Follow up with: "How do I know how many stages my job will have?" Answer: count the shuffles (groupBy, join, repartition) + 1.

What NOT to Say: "Each transformation creates a new stage." No β€” narrow transformations are pipelined within one stage.

Answer First: Simple Explanation: Two different knobs for two different engines. shuffle.partitions controls the DataFrame engine (SQL operations). If you are using DataFrames (you should be), focus on shuffle.partitions.

Memory Map: spark.sql.shuffle.partitions vs spark.default.parallelism -> SQL exchanges use the configured reducer count -> RDD operations derive defaults from available parallelism -> API and operator determine which knob applies -> physical plan confirms the chosen partition count [01_Spark_Architecture_and_Internals.md:1374].

Q36: spark.sql.shuffle.partitions vs spark.default.parallelism?

Simple Explanation: Two different knobs for two different engines. shuffle.partitions controls the DataFrame engine (SQL operations). default.parallelism controls the RDD engine. If you are using DataFrames (you should be), focus on shuffle.partitions.

Technical Answer: shuffle.partitions (default 200) = for DataFrame shuffle operations. default.parallelism = for RDD operations (default = total cores). They are independent settings.

Interview Tip: With AQE, shuffle.partitions is less critical β€” set it high and let AQE coalesce.

What NOT to Say: "They are the same thing." They control completely different subsystems.

Answer First: Simple Explanation: Think of a restaurant: a Job = one customer order (triggered by one action like count() ). A Stage = one course of the meal (appetizer, main, dessert β€” separated by shuffles). A Task = one dish within a course (one partition of work).

Memory Map: a task, stage, job -> one action creates a job -> exchange boundaries divide it into stages -> each partition becomes a task -> scheduler events show the resulting hierarchy [01_Spark_Architecture_and_Internals.md:1384].

Q37: What is a task, stage, job?

Simple Explanation: Think of a restaurant: a Job = one customer order (triggered by one action like count()). A Stage = one course of the meal (appetizer, main, dessert β€” separated by shuffles). A Task = one dish within a course (one partition of work).

Technical Answer:

  • Job = one action (e.g., count(), write())
  • Stage = set of tasks that can run in parallel without shuffle
  • Task = one unit of work on one partition

Interview Tip: They may ask "How many tasks will this job have?" Answer: number of partitions in the largest stage.

What NOT to Say: "A task is the same as a stage." A stage contains many tasks (one per partition).

Answer First: Spark waits 3 seconds before downgrading to a worse locality level.

Memory Map: data locality levels -> scheduler compares task preferences with executor hosts -> process-local and node-local placement avoid transfers -> rack or any placement increases distance -> locality wait and fetch metrics quantify compromise [01_Spark_Architecture_and_Internals.md:1397].

Q38: Explain data locality levels.

Simple Explanation: Think of where you store information relative to you:

  • PROCESS_LOCAL = reading your own notes on your desk (fastest β€” data is in the same JVM)
  • NODE_LOCAL = grabbing a file from the filing cabinet next to you (same machine, different JVM)
  • NO_PREF = no preference β€” data has no location preference
  • RACK_LOCAL = walking to the filing cabinet in the next room (same rack, different machine)
  • ANY = calling another office across town (any node β€” slowest)

Spark waits 3 seconds before downgrading to a worse locality level.

Technical Answer: PROCESS_LOCAL (data in same JVM) > NODE_LOCAL (same node, different JVM) > NO_PREF (no preference) > RACK_LOCAL (same rack) > ANY (any node). Spark waits spark.locality.wait (3s) before downgrading.

Interview Tip: Mention that with cloud storage (S3), everything is ANY β€” data locality only matters with HDFS.

What NOT to Say: "Data locality does not matter anymore." It still matters significantly for HDFS-based clusters and cached data.

Answer First: Simple Explanation: explain() is like asking your GPS to show you the planned route before driving.

Memory Map: explain() help -> parsed and analyzed trees reveal resolution -> optimized tree reveals rule rewrites -> physical tree reveals joins scans and exchanges -> extended output connects estimates to runtime nodes [01_Spark_Architecture_and_Internals.md:1414].

Q39: How does explain() help?

Simple Explanation: explain() is like asking your GPS to show you the planned route before driving. You can see whether it chose the highway (broadcast join) or side streets (sort-merge join), whether it is avoiding toll roads (predicate pushdown), and whether it is taking unnecessary detours.

Technical Answer: Shows logical and physical plans. df.explain(True) shows all 4 phases: Parsed β†’ Analyzed β†’ Optimized β†’ Physical. df.explain("cost") includes CBO statistics. Look for: join strategy, predicate pushdown, partition pruning, codegen nodes.

python β€” editable
df.explain(True)       # ← Show all 4 Catalyst phases
df.explain("cost")     # ← Include CBO statistics (row counts, sizes)
df.explain("formatted") # ← Pretty-printed physical plan
# Key things to look for:
# *(1) = codegen enabled (good)
# BroadcastHashJoin = small table broadcasted (good for small+large joins)
# SortMergeJoin = both tables shuffled (check if one should be broadcast)
# PushedFilters = filters pushed to data source (good)

Interview Tip: "The first thing I do when debugging a slow query is run explain(True)." This sentence alone tells the interviewer you know what you are doing.

What NOT to Say: "I use explain() to see the output of my query." No β€” explain() shows the execution plan, not the data.

Answer First: Simple Explanation: Imagine you have a massive warehouse (fact table) and a small catalog (dimension table). You want all items from the catalog where category = "Electronics." Without DPP, Spark scans the ENTIRE warehouse.

Memory Map: Dynamic Partition Pruning (DPP) -> dimension-side filtering produces qualifying partition keys -> runtime subquery sends those keys to the fact scan -> irrelevant directories are skipped -> scan files and bytes prove pruning [01_Spark_Architecture_and_Internals.md:1435].

Q40: What is Dynamic Partition Pruning (DPP)?

Simple Explanation: Imagine you have a massive warehouse (fact table) and a small catalog (dimension table). You want all items from the catalog where category = "Electronics." Without DPP, Spark scans the ENTIRE warehouse. With DPP, Spark first checks the catalog to find which shelves have Electronics, then ONLY visits those shelves in the warehouse.

Technical Answer: (Spark 3.0+) When a fact table joins with a filtered dimension table, DPP pushes the dimension filter result into the fact table scan at runtime.

Without DPP: Full scan of fact_sales β†’ join β†’ filter With DPP: Spark first computes filtered dim_date IDs β†’ uses them to prune fact_sales partitions during scan

sql
-- DPP kicks in automatically for this pattern:
SELECT * FROM fact_sales f              -- ← Huge fact table (partitioned by date_id)
JOIN dim_date d ON f.date_id = d.id     -- ← Small dimension table
WHERE d.year = 2024;                    -- ← Filter on the dimension
-- Without DPP: scan ALL partitions of fact_sales, then join, then filter
-- With DPP: Spark first finds 2024 date_ids from dim_date,
--           then scans ONLY fact_sales partitions matching those date_ids
-- Result: reads 1/10th of the data!

Config: spark.sql.optimizer.dynamicPartitionPruning.enabled=true (default in Spark 3.x)

Interview Tip: DPP is most effective when the fact table is partitioned by the join key. Mention this requirement.

What NOT to Say: "DPP and predicate pushdown are the same thing." Predicate pushdown is compile-time. DPP is runtime β€” it uses the result of one query to prune another.

Advanced Spark Operations & Coding

πŸ’‘ Interview Tip
Focus: DataFrame API, Joins, Windows, Streaming, UDFs, Coding Challenges Approach: Every topic starts with simple explanation β†’ then interview-level depth

MEMORY MAP: PYSPARK OPERATIONS β†’ JAWS-UC

🧠 PYSPARK OPERATIONS β†’ JAWS-UC
PYSPARK OPERATIONSJAWS-UC
JJoins (5 types + 4 physical strategies)
AAggregations (groupBy, window, pivot, cube)
WWindow Functions (row_number, rank, lag/lead)
SStreaming (Structured Streaming + Auto Loader)
UUDFs (Python UDF, Pandas UDF, why UDFs are slow)
CCoding Patterns (dedup, SCD, top-N, gap detection)

SECTION 1: DATAFRAME API DEEP DIVE

Answer First: You have many columns and you want to transform or pick certain ones.

Memory Map: the differences between select(), withColumn(), and selectExpr()? When is each appropriate -> select projects existing expressions together -> withColumn adds or replaces one named expression -> selectExpr parses SQL expression strings -> plan shape and readability choose the API [02_PySpark_Advanced_Operations.md:27].

Q1: What are the differences between select(), withColumn(), and selectExpr()? When is each appropriate?

Simple Explanation: Think of a spreadsheet. You have many columns and you want to transform or pick certain ones.

  • select() = "Give me ONLY these columns" β€” like highlighting specific columns and copying them to a new sheet
  • withColumn() = "Keep everything, but add/change ONE column" β€” like inserting a new column into the existing sheet
  • selectExpr() = "Give me these columns, but let me write SQL for them" β€” like using formulas in Excel

Technical details:

  • select(): Projects specific columns. Accepts Column objects or strings. Use when you want a subset of columns or need multiple transformations.
  • withColumn(): Adds or replaces a single column. Returns the full DataFrame with the new/modified column.
  • selectExpr(): Like select() but accepts SQL expression strings. Quick for ad-hoc: selectExpr("*", "col1 + col2 as sum_col").
python β€” editable
# select() β€” pick and transform columns
df.select("name", "age", (col("salary") * 1.1).alias("new_salary"))
# ← Returns ONLY name, age, new_salary (everything else is dropped)

# withColumn() β€” add/modify ONE column, keep everything
df.withColumn("new_salary", col("salary") * 1.1)
# ← Returns ALL original columns + new_salary

# selectExpr() β€” SQL expressions as strings
df.selectExpr("*", "salary * 1.1 as new_salary", "UPPER(name) as name_upper")
# ← Handy for quick SQL-style transforms without importing functions

Sample data flow:

Original: | name | age | salary |
| Alice | 30 | 50000 |
select(): | name | age | new_salary | ← only selected columns
| Alice | 30 | 55000 |
withColumn(): | name | age | salary | new_salary | ← ALL columns + new one
| Alice | 30 | 50000 | 55000 |

Interview Tip: If asked "when do you use each?", say: "select() for projections and multiple transforms in one shot, withColumn() for adding a single column while keeping everything, and selectExpr() when you want quick SQL expressions without importing functions."

What NOT to Say: "They're all the same." They have very different impacts on the logical plan and performance (see Q2).

Answer First: Instead of saying "Build a house with 3 rooms, 2 bathrooms, and a kitchen" (one instruction), you say "Build room 1.

Memory Map: is chaining multiple withColumn() calls a performance anti-pattern? What's the fix -> repeated withColumn calls deepen projection lineage -> analyzer repeatedly resolves expanding plans -> one select builds a flat projection -> analysis time and plan depth verify improvement [02_PySpark_Advanced_Operations.md:74].

Q2: Why is chaining multiple withColumn() calls a performance anti-pattern? What's the fix?

  1. Simple Explanation: Imagine you're giving instructions to a builder. Instead of saying "Build a house with 3 rooms, 2 bathrooms, and a kitchen" (one instruction), you say "Build room
  2. 1Now add room
  3. 2Now add room
  4. 3Now add bathroom 1..." β€” each instruction creates a new blueprint that wraps around the previous one. After 50 instructions, the builder is drowning in 50 nested blueprints.

That's exactly what happens inside Spark's query planner. Each withColumn() creates a new Project node in the logical plan. 50 calls = 50 nested Project nodes that Catalyst must crawl through.

Technical details:

Each withColumn() creates a new Project node in the logical plan. Chaining 50+ calls creates a deeply nested plan that Catalyst must analyze and optimize:

  • Extremely slow query planning (minutes)
  • Possible StackOverflowError during plan traversal

Visual: What the logical plan looks like

BAD β€” 4 withColumn() calls create 4 nested Project nodes:
Project [*, d = ...] ← withColumn("d", ...)
Project [*, c = ...] ← withColumn("c", ...)
Project [*, b = ...] ← withColumn("b", ...)
Project [*, a = ...] ← withColumn("a", ...)
Scan table
GOOD β€” 1 select() call creates 1 Project node:
Project [*, a = ..., b = ..., c = ..., d = ...] ← single select()
Scan table

Bad:

python β€” editable
df = df.withColumn("a", expr("..."))  # ← creates Project node 1
df = df.withColumn("b", expr("..."))  # ← creates Project node 2 wrapping node 1
df = df.withColumn("c", expr("..."))  # ← creates Project node 3 wrapping node 2
# ... 50 more times β†’ StackOverflowError or minutes of planning time

Good:

python β€” editable
df = df.select(
    "*",
    expr("...").alias("a"),  # ← all transforms in ONE Project node
    expr("...").alias("b"),
    expr("...").alias("c"),
    # all at once
)

Or using functools.reduce:

python β€” editable
from functools import reduce
transforms = [("a", expr("...")), ("b", expr("...")), ("c", expr("..."))]
df = reduce(lambda d, t: d.withColumn(t[0], t[1]), transforms, df)
# ← Still creates nested nodes but cleaner code. For extreme cases, use select().

Interview Tip: This is a VERY common question. Show you know the internal reason (nested Project nodes in the logical plan), not just "it's slow." If you can say "I've seen this cause StackOverflowError in production with 100+ columns," that's even better.

What NOT to Say: "withColumn is always bad." It's fine for 1-5 columns. The anti-pattern is chaining 50+ calls.

Answer First: A join combines two tables based on a matching key β€” like matching a guest list (Table A) with a seating chart (Table B) to figure out who sits where. Different join types answer different questions about what happens when someone is on one list but not the other.

Memory Map: all types of joins in PySpark and their physical implementations -> logical join semantics determine retained rows -> size and ordering determine broadcast hash or merge execution -> exchanges align unmatched partitions -> physical operators and row counts validate behavior [02_PySpark_Advanced_Operations.md:136].

Q3: Explain all types of joins in PySpark and their physical implementations.

Simple Explanation: A join combines two tables based on a matching key β€” like matching a guest list (Table A) with a seating chart (Table B) to figure out who sits where. Different join types answer different questions about what happens when someone is on one list but not the other.

Join Types β€” Visual with Actual Data:

Let's use two small tables:

emp_idnamedept_iddept_iddept_name
1Alice1010Engineering
2Bob2020Marketing
3Charlie3040Finance
4DianaNULL

Each join type β€” what comes out:

🧠 Memory Map
INNER JOIN (only matches)
| emp_id | name | dept_id | dept_name |
| 1 | Alice | 10 | Engineering | ← both tables have dept 10
| 2 | Bob | 20 | Marketing | ← both tables have dept 20
# Charlie (dept 30) dropped β€” no match in departments
# Diana (NULL) dropped β€” NULL never matches
# Finance (dept 40) dropped β€” no match in employees
LEFT OUTER JOIN (all from left, match from right)
| emp_id | name | dept_id | dept_name |
| 1 | Alice | 10 | Engineering |
| 2 | Bob | 20 | Marketing |
| 3 | Charlie | 30 | NULL | ← no dept 30 in rightβ†’NULL
| 4 | Diana | NULL | NULL | ← NULL keyβ†’no match β†’ NULL
RIGHT OUTER JOIN (all from right, match from left)
| emp_id | name | dept_id | dept_name |
| 1 | Alice | 10 | Engineering |
| 2 | Bob | 20 | Marketing |
| NULL | NULL | 40 | Finance | ← no emp with dept 40β†’NULLs
FULL OUTER JOIN (everything from both sides)
| emp_id | name | dept_id | dept_name |
| 1 | Alice | 10 | Engineering |
| 2 | Bob | 20 | Marketing |
| 3 | Charlie | 30 | NULL | ← left only
| 4 | Diana | NULL | NULL | ← left only (NULL key)
| NULL | NULL | 40 | Finance | ← right only
LEFT SEMI JOIN (left rows that HAVE a match β€” like SQL "IN")
| emp_id | name | dept_id |
| 1 | Alice | 10 | ← dept 10 exists in departments
| 2 | Bob | 20 | ← dept 20 exists in departments
# Notice: NO columns from right table appear
LEFT ANTI JOIN (left rows that have NO match β€” like SQL "NOT IN")
| emp_id | name | dept_id |
| 3 | Charlie | 30 | ← dept 30 NOT in departments
| 4 | Diana | NULL | ← NULL NOT in departments
CROSS JOIN (cartesian product β€” every row x every row)
| emp_id | name | dept_id | dept_name |
| 1 | Alice | 10 | Engineering |
| 1 | Alice | 10 | Marketing |
| 1 | Alice | 10 | Finance |
| 2 | Bob | 20 | Engineering |
... (4 employees Γ— 3 departments = 12 rows total)

Physical Implementations (How Spark Actually Executes the Join):

StrategyWhen UsedShuffle?Notes
Broadcast Hash Join (BHJ)One side < 10 MB (default)NOFastest. Small side broadcast to all executors.
Sort-Merge Join (SMJ)Both sides large, equi-joinYESDefault for large-large. Both sides sorted by join key.
Shuffle Hash JoinOne side significantly smallerYESHash table built from smaller side per partition.
Broadcast Nested Loop (BNLJ)Non-equi join, one side smallNOBroadcast small side, nested loop.
Cartesian ProductCross join or non-equi, both largeYESExtremely expensive. Avoid if possible.

Decision Tree β€” Which join strategy does Spark pick?

πŸ—‚οΈWhich join strategy does Spark pick?
One side < 10 MB? β†’ BROADCAST HASH JOIN (fastest)
Both large + equi-join + keys sorted? β†’ SORT MERGE JOIN (default for large)
Both large + equi-join + keys not sorted? β†’ SHUFFLE HASH JOIN
Non-equi join? β†’ BROADCAST NESTED LOOP or CARTESIAN

Analogies to remember:

  • Broadcast join = Teacher distributing handouts to every student (small table sent everywhere). No students need to move β€” the handout comes to them.
  • Sort Merge join = Two people merging sorted card decks. Both decks are sorted by number, you walk through both simultaneously matching pairs. Fast, but you need to sort first.
  • Shuffle = Moving furniture between apartments (expensive!). Data has to physically move across the network to land on the right executor.

Force a broadcast:

python β€” editable
from pyspark.sql.functions import broadcast
result = large_df.join(broadcast(small_df), "key")
# ← Forces Spark to broadcast small_df even if statistics say otherwise

Interview Tip: Draw the decision tree on the whiteboard. Interviewers love seeing you reason about which physical strategy Spark will pick. Mention that you can check the strategy with df.explain() β€” the plan will say "BroadcastHashJoin" or "SortMergeJoin."

What NOT to Say: "I always use broadcast join because it's faster." Broadcast join can cause driver OOM if the table is too large. You need to understand WHEN each strategy applies.

Answer First: Spark automatically broadcasts a table if it thinks the table is smaller than 10 MB. The idea is: "If this table is tiny, just send a copy to every executor β€” no need to shuffle the big table." But this can go wrong in several ways.

Memory Map: the default broadcast join threshold? What are the pitfalls of broadcast joins -> estimated small-side bytes are compared with the threshold -> driver materializes and distributes the relation -> every executor builds a local lookup -> memory pressure and stale estimates expose risk [02_PySpark_Advanced_Operations.md:244].

Q4: What is the default broadcast join threshold? What are the pitfalls of broadcast joins?

Simple Explanation: Spark automatically broadcasts a table if it thinks the table is smaller than 10 MB. The idea is: "If this table is tiny, just send a copy to every executor β€” no need to shuffle the big table." But this can go wrong in several ways.

Think of it like sending a printed copy of a document to every employee in the building. If the document is 2 pages, great. If it turns out to be 200 pages, you've just overwhelmed the print room (the driver).

Technical details:

Default: spark.sql.autoBroadcastJoinThreshold = 10 MB (10485760 bytes)

Pitfalls:

  1. Statistics can be wrong β€” Spark uses file size, not post-filter size. A 1 GB table filtered down to 1 MB? Spark still sees 1 GB and won't broadcast.
  2. Driver OOM β€” Driver collects the broadcast table before sending. If the table is bigger than expected, the driver crashes.
  3. Memory per executor β€” Total memory = table_size x num_executors (broadcast replicated everywhere). A 9 MB table with 100 executors = 900 MB of cluster memory used.
  4. Dynamic size β€” Table that was 5 MB yesterday might be 500 MB tomorrow.

When NOT to broadcast:

  • When the "small" table size is unpredictable
  • When the table is actually large after transformations
  • When the driver has limited memory
python β€” editable
# Check what Spark thinks the table size is
df.explain(True)  # ← look for "Statistics(sizeInBytes=...)" in the plan

# Disable broadcast entirely if causing issues
spark.conf.set("spark.sql.autoBroadcastJoinThreshold", "-1")

# Or increase threshold if you have big executors
spark.conf.set("spark.sql.autoBroadcastJoinThreshold", "100m")

Interview Tip: If asked about broadcast joins, always mention the driver OOM risk. This shows production experience β€” it's a classic gotcha that only people who've debugged real pipelines know about.

What NOT to Say: "I set the broadcast threshold to 1 GB for everything." This is dangerous and shows you don't understand the driver memory implications.

Answer First: Data skew means one key has MUCH more data than others. Imagine a post office where 90% of all mail goes to one zip code. That one mail carrier is overwhelmed while the others are idle.

Memory Map: you handle skewed data in a join? Explain ALL techniques -> frequency analysis identifies dominant keys -> salting or skew splitting divides heavy groups -> adaptive handling isolates oversized partitions -> task-size distribution verifies balance [02_PySpark_Advanced_Operations.md:283].

Q5: How do you handle skewed data in a join? Explain ALL techniques.

Simple Explanation: Data skew means one key has MUCH more data than others. Imagine a post office where 90% of all mail goes to one zip code. That one mail carrier is overwhelmed while the others are idle.

In Spark, this means one partition has millions of rows while others have thousands. The one overloaded partition becomes the bottleneck β€” the entire job waits for it to finish.

Analogy: Shuffle = Moving furniture between apartments. Skew = 99% of the furniture goes to one apartment while 99 other apartments get almost nothing. That one apartment is overloaded and takes forever.

Technique 1: Salting (Most Common)

python β€” editable
from pyspark.sql.functions import lit, rand, floor, explode, array, col

salt_buckets = 10

# Step 1: Salt the large (skewed) side β€” add random number 0-9 to the key
large_df = large_df.withColumn("salt", floor(rand() * salt_buckets).cast("int"))
# ← Key "USA" becomes "USA_0", "USA_1", ..., "USA_9" β€” splits hot key into 10 partitions

# Step 2: Replicate the small side for each salt value
small_df = small_df.withColumn(
    "salt", explode(array([lit(i) for i in range(salt_buckets)]))
)
# ← Each row in small_df is copied 10 times (one for each salt value)

# Step 3: Join on key + salt
result = large_df.join(small_df, ["key", "salt"]).drop("salt")
# ← The hot key is now spread across 10 partitions instead of 1

Before salting vs after:

🧠 Memory Map
BEFORE (skewed)
Partition 0: key="USA"β†’10 million rows ← BOTTLENECK
Partition 1: key="UK"β†’1,000 rows
Partition 2: key="JP"β†’1,000 rows
AFTER (salted with 10 buckets)
Partition 0: key="USA", salt=0β†’1 million rows ← evenly split!
Partition 1: key="USA", salt=1β†’1 million rows
...
Partition 9: key="USA", salt=9β†’1 million rows
Partition 10: key="UK", salt=0β†’1,000 rows

Technique 2: AQE Skew Join (Easiest)

python β€” editable
spark.conf.set("spark.sql.adaptive.enabled", "true")          # ← enable AQE
spark.conf.set("spark.sql.adaptive.skewJoin.enabled", "true")  # ← enable auto skew handling
spark.conf.set("spark.sql.adaptive.skewJoin.skewedPartitionFactor", "5")
# ← a partition is "skewed" if it's 5x larger than the median partition
spark.conf.set("spark.sql.adaptive.skewJoin.skewedPartitionThresholdInBytes", "256m")
# ← AND the partition is larger than 256 MB

Technique 3: Isolate-and-Union

python β€” editable
# Step 1: Identify skewed keys (from analysis of your data)
skewed_keys = ["key1", "key2"]

# Step 2: Process skewed keys with broadcast join (fast, no shuffle)
skewed_result = large_df.filter(col("key").isin(skewed_keys)) \
    .join(broadcast(small_df), "key")

# Step 3: Process non-skewed keys normally (sort-merge join)
normal_result = large_df.filter(~col("key").isin(skewed_keys)) \
    .join(small_df, "key")

# Step 4: Combine both results
result = skewed_result.union(normal_result)

Technique 4: Two-Phase Aggregation (for groupBy skew)

python β€” editable
from pyspark.sql.functions import concat, lit, floor, rand, split, sum as _sum

# Phase 1: Partial aggregation with salt (splits hot keys)
salted = df.withColumn("salted_key", concat(col("key"), lit("_"), floor(rand() * 100).cast("string")))
partial = salted.groupBy("salted_key").agg(_sum("value").alias("partial_sum"))
# ← "USA" is now "USA_0" through "USA_99" β€” 100 partial sums

# Phase 2: Remove salt, final aggregation
result = partial.withColumn("key", split(col("salted_key"), "_")[0]) \
    .groupBy("key").agg(_sum("partial_sum").alias("total_sum"))
# ← Recombine "USA_0"..."USA_99" back into "USA" with final sum

Interview Tip: Start with "In Databricks, I'd first enable AQE which handles skew automatically. For extreme cases, I'd use salting." This shows you know the modern approach AND the manual technique.

What NOT to Say: "I'd just increase the number of partitions." Repartitioning doesn't fix skew β€” if one key has 90% of data, it still lands on one partition regardless of how many partitions you have.

SECTION 2: WINDOW FUNCTIONS

Answer First: Window functions let you do calculations WITHIN groups without collapsing the groups. Think of it as: "Within each department, rank employees by salary" β€” you still see every employee, but each one now has a rank number.

Memory Map: window functions. What's the difference between row_number(), rank(), and dense_rank() -> partition key defines peers -> ordering establishes sequence -> frame selects visible rows -> analytic value returns without collapsing input [02_PySpark_Advanced_Operations.md:378].

Q6: Explain window functions. What's the difference between row_number(), rank(), and dense_rank()?

Simple Explanation: Window functions let you do calculations WITHIN groups without collapsing the groups. Think of it as: "Within each department, rank employees by salary" β€” you still see every employee, but each one now has a rank number.

Analogy: Window functions = "Within each group, rank/number the rows." Like a class of students β€” you want to rank each student within their own class, not across the whole school.

Technical details:

python β€” editable
from pyspark.sql import Window
from pyspark.sql.functions import row_number, rank, dense_rank

w = Window.partitionBy("department").orderBy(col("salary").desc())
# ← "Within each department, order by salary highest first"

Step-by-step data walkthrough:

πŸ“‹ Overview
INPUT DATA
| name | department | salary |
|---------|-----------|--------|
| Alice | Eng | 100 |
| Bob | Eng | 90 |
| Charlie | Eng | 90 |
| Diana | Eng | 80 |
| Eve | Sales | 95 |
| Frank | Sales | 85 |
STEP 1 β€” partitionBy("department"):
Group 1 (Eng): Alice(100), Bob(90), Charlie(90), Diana(80)
Group 2 (Sales): Eve(95), Frank(85)
STEP 2 β€” orderBy(salary.desc()) within each partition:
Group 1 (Eng): Alice(100) β†’ Bob(90) β†’ Charlie(90) β†’ Diana(80)
Group 2 (Sales): Eve(95) β†’ Frank(85)
STEP 3 β€” Apply the function:
namedepartmentsalaryrow_numberrankdense_rank
AliceEng100111
BobEng90222
CharlieEng90322
DianaEng80443
EveSales95111
FrankSales85222

Key differences with ties (Bob and Charlie both have salary 90):

FunctionResult for tiesGaps?Description
row_number()2, 3N/A β€” always uniqueArbitrary tiebreaker β€” one gets 2, the other gets 3
rank()2, 2 β†’ skip to 4YES, gaps after tiesLike Olympic medals β€” two silvers, no bronze
dense_rank()2, 2 β†’ next is 3NO gapsLike counting distinct salary levels
python β€” editable
# Code to produce the above:
result = df.select(
    "*",
    row_number().over(w).alias("row_number"),   # ← 1,2,3,4 (always unique)
    rank().over(w).alias("rank"),                # ← 1,2,2,4 (gaps after ties)
    dense_rank().over(w).alias("dense_rank"),    # ← 1,2,2,3 (no gaps)
)

Interview Tip: "Which one should I use for deduplication?" Always row_number() β€” it guarantees exactly one row per group (no ties). For "top N by category" where you want ties, use dense_rank().

What NOT to Say: "They're basically the same." Choosing the wrong one in a dedup query can give you duplicate results (rank/dense_rank don't guarantee uniqueness with ties).

Answer First: A running total uses an ordered, unbounded-preceding window; a moving average changes the frame, and percent-of-total uses a partition-wide sum. Reusing one partition and ordering specification can avoid redundant exchanges.

Memory Map: PySpark code to compute running total, 7-day moving average, and percentage of total β€” all in one pass -> shared partition and order define one window -> frame clauses derive cumulative and moving ranges -> aggregate expressions reuse the same sort -> physical plan confirms one exchange and ordering [02_PySpark_Advanced_Operations.md:452].

Q7: Write PySpark code to compute running total, 7-day moving average, and percentage of total β€” all in one pass.

Simple Explanation: Imagine a sales dashboard. For each day, you want to see:

  1. Running total β€” "How much have we sold from day 1 until today?"
  2. 7-day moving average β€” "What's the average daily sales over the last 7 days?"
  3. Percentage of total β€” "What % of the entire year's sales happened today?"

Window functions let you compute ALL of these in a single query β€” no self-joins, no subqueries.

Technical details:

python β€” editable
from pyspark.sql import Window
from pyspark.sql.functions import sum as _sum, avg, col

# Window 1: Running total (all rows from start up to current row)
cumulative_w = Window.partitionBy("category").orderBy("date") \
    .rowsBetween(Window.unboundedPreceding, Window.currentRow)
# ← "From the very first row to the current row"

# Window 2: 7-day moving average (current row + 6 prior rows = 7 rows)
moving_w = Window.partitionBy("category").orderBy("date") \
    .rowsBetween(-6, Window.currentRow)
# ← "From 6 rows back to the current row"

# Window 3: Total for percentage (ALL rows in the partition, no order)
total_w = Window.partitionBy("category")
# ← "Every single row in this category"

result = df.select(
    "*",
    _sum("revenue").over(cumulative_w).alias("running_total"),
    avg("revenue").over(moving_w).alias("moving_avg_7d"),
    (col("revenue") / _sum("revenue").over(total_w) * 100).alias("pct_of_total")
)

Sample data flow:

categorydaterevenue
Books2026-01-01100
Books2026-01-02150
Books2026-01-03200
Books2026-01-0450
categorydaterevenue
Books2026-01-01100
Books2026-01-02150
Books2026-01-03200
Books2026-01-0450

Interview Tip: Mention that this runs in ONE pass over the data. Without window functions, you'd need multiple self-joins or subqueries β€” much slower and harder to read.

What NOT to Say: "I'd use a groupBy and then join back." That works but is far less efficient than window functions for this use case.

Answer First: Both define the "window frame" β€” which rows the function looks at. The difference is HOW they count.

Memory Map: the difference between rowsBetween and rangeBetween -> rowsBetween counts physical row offsets -> rangeBetween compares ordering-value distances -> duplicate order values change range membership -> boundary examples reveal different frames [02_PySpark_Advanced_Operations.md:516].

Q8: What is the difference between rowsBetween and rangeBetween?

Simple Explanation: Both define the "window frame" β€” which rows the function looks at. The difference is HOW they count:

  • rowsBetween = counts by physical position (row 1, row 2, row 3...)
  • rangeBetween = counts by value (all rows where the value is within a certain range)

Analogy: Imagine you're at a concert. rowsBetween(-2, 0) = "me and the 2 people directly in front of me" (physical seats). rangeBetween(-2, 0) = "me and everyone whose ticket price is within $2 of mine" (based on value, not position).

Technical details:

daterevenue
2026-01-01100
2026-01-02150
2026-01-04200
2026-01-0550
  • rowsBetween: Physical offset by row count. -6, 0 = current row and 6 rows before.
  • rangeBetween: Logical offset by value. -6, 0 = current value and values up to 6 less.

Critical difference: With rangeBetween, if your data has gaps (e.g., missing dates), the window adjusts logically. With rowsBetween, it always uses the physical row positions.

python β€” editable
from pyspark.sql.functions import unix_timestamp

# Convert date to seconds for rangeBetween
days_7 = 7 * 86400  # ← 7 days in seconds
w = Window.partitionBy("category") \
    .orderBy(unix_timestamp("date")) \
    .rangeBetween(-days_7, 0)
# ← "All rows within 7 calendar days before the current row"

Interview Tip: If asked "How do you compute a 7-day moving average when dates have gaps?", use rangeBetween with unix_timestamp. This is a common follow-up that trips people up.

What NOT to Say: "I'd use rowsBetween(-6, 0) for a 7-day window." That only works if you have data for every single day with no gaps.

Answer First: Partition purchases by customer and order them by timestamp. Window ranks identify the first and last purchases, while lag or a second-ranked row supplies the first-to-second interval.

Memory Map: Scenario β€” Find the first and last purchase per customer, plus the time between their first and second purchase -> customer partition orders purchases by timestamp -> row numbers identify first and last events -> lag exposes the second-purchase interval -> sample customers verify retention measures [02_PySpark_Advanced_Operations.md:571].

Q9: Scenario β€” Find the first and last purchase per customer, plus the time between their first and second purchase.

Simple Explanation: For each customer, we want: when did they first buy, when did they last buy, and how many days between purchase #1 and purchase #2? This tells us about customer retention β€” a short gap means they came back quickly.

Technical details:

python β€” editable
from pyspark.sql import Window
from pyspark.sql.functions import first, last, lead, datediff, col, row_number

w = Window.partitionBy("customer_id").orderBy("purchase_date")
# ← "Within each customer, sort purchases chronologically"

result = df.withColumn("purchase_rank", row_number().over(w)) \
    .withColumn("first_purchase", first("purchase_date").over(
        w.rowsBetween(Window.unboundedPreceding, Window.unboundedFollowing)
    )) \
    .withColumn("last_purchase", last("purchase_date").over(
        w.rowsBetween(Window.unboundedPreceding, Window.unboundedFollowing)
    )) \
    .withColumn("next_purchase", lead("purchase_date", 1).over(w)) \
    .filter(col("purchase_rank") == 1) \
    .withColumn("days_to_second_purchase",
        datediff(col("next_purchase"), col("purchase_date"))
    )

Sample data flow:

customer_idpurchase_date
C12026-01-01
C12026-01-15
C12026-03-01
customer_idpurchase_date
C12026-01-01
C12026-01-15
C12026-03-01
customer_idfirst_purchase
C12026-01-01

Interview Tip: Mention that lead() looks at the NEXT row while lag() looks at the PREVIOUS row. For "time to second purchase," you need lead from the first row, not lag from the second.

What NOT to Say: "I'd do a self-join." Window functions are far more efficient than self-joins for this pattern.

SECTION 3: PARTITIONING & BUCKETING

Answer First: Both change the number of partitions in your DataFrame. Think of partitions as boxes of data.

Memory Map: repartition() vs coalesce(). When do you use each -> repartition vs coalesce use each determines row placement and downstream task balance -> distribution rule assigns rows -> partition count controls task size -> shuffle or pruning changes runtime work -> task-size spread validates balance [02_PySpark_Advanced_Operations.md:629].

Q10: Explain repartition() vs coalesce(). When do you use each?

Simple Explanation: Both change the number of partitions in your DataFrame. Think of partitions as boxes of data.

  • repartition() = Dump ALL boxes out, then redistribute evenly into new boxes. Expensive (requires a full shuffle) but guarantees even distribution.
  • coalesce() = Merge adjacent boxes together WITHOUT moving most of the data. Cheap but can create uneven boxes.

Analogy: Moving into a new house.

  • repartition(4) = Unpack everything, then repack into exactly 4 evenly-filled boxes. Takes time but organized.
  • coalesce(4) = Just combine nearby boxes. Box 1 stays as is, box 2 gets merged into box 3, etc. Fast but some boxes might be overstuffed.

Technical details:

Aspectrepartition(n)coalesce(n)
ShuffleYES (full shuffle)NO (narrow transformation)
Increase partitions?YesNo (only decrease)
Even distribution?YesNo (merges adjacent partitions, can be uneven)
By column?Yes: repartition(n, "col")No
Use caseNeed even distribution, increase partitions, join optimizationReduce partitions before write

Repartition by column (hash-partitioned):

python β€” editable
# Co-locate all rows with same user_id on same partition
df = df.repartition(100, "user_id")
# ← Now a groupBy("user_id") or join on "user_id" won't need shuffle

# Common pattern: reduce partitions before writing to avoid small files
df.coalesce(10).write.format("delta").save("/path")
# ← Merges down to 10 partitions β†’ 10 output files (no shuffle!)

Interview Tip: The most common use of coalesce() is right before a write to reduce the number of small output files. Say: "After a filter that reduces data by 90%, I coalesce to avoid writing thousands of tiny files."

What NOT to Say: "I use repartition before every write." That triggers an unnecessary shuffle. Use coalesce when reducing partitions.

Answer First: Bucketing is like pre-sorting your filing cabinet by category ONCE, so you never have to sort it again.

Memory Map: bucketing? How does it eliminate shuffles -> write-time hashing assigns rows to stable bucket files -> compatible tables share key distribution -> planner can avoid a later exchange -> bucket count and join plan confirm reuse [02_PySpark_Advanced_Operations.md:668].

Q11: What is bucketing? How does it eliminate shuffles?

Simple Explanation: Bucketing is like pre-sorting your filing cabinet by category ONCE, so you never have to sort it again. When you save a table, you tell Spark: "Organize this data into N buckets by this column." From then on, any join or groupBy on that column is already organized β€” no shuffle needed.

Technical details:

Bucketing pre-partitions data into a fixed number of buckets by hash of specified columns, and optionally sorts within each bucket.

python β€” editable
df.write.bucketBy(256, "user_id").sortBy("user_id").saveAsTable("bucketed_users")
# ← Data is written into 256 buckets, each containing specific user_ids
# ← Bucket assignment: bucket_number = hash(user_id) % 256

How it eliminates shuffles: When two bucketed tables with the same bucket count and bucket column are joined, Spark performs a Sort-Merge Join WITHOUT shuffle β€” data with the same key is already co-located.

WITHOUT bucketing:
Table A (random order) ──shuffle──┐
β”œβ”€β”€ Sort-Merge Join
Table B (random order) ──shuffleβ”€β”€β”˜
Time: shuffle A + shuffle B + join = SLOW
WITH bucketing (both tables bucketed by user_id, 256 buckets):
Table A bucket 0 ─┐
Table B bucket 0 β”€β”˜β”€β”€ join (no shuffle, already co-located!)
Table A bucket 1 ─┐
Table B bucket 1 β”€β”˜β”€β”€ join (no shuffle!)
... Γ— 256 buckets
Time: just the join = FAST

Caveats:

  • Only works with Hive-managed tables (saveAsTable, not save)
  • Bucket count must match between tables
  • spark.sql.sources.bucketing.enabled must be true
  • In Databricks, consider Liquid Clustering as a modern alternative

Interview Tip: Mention Liquid Clustering as the Databricks-native replacement for bucketing + Z-ORDER. It's simpler and auto-tunes. Shows you know modern Databricks features.

What NOT to Say: "I always bucket every table." Bucketing adds overhead on write and only helps if you frequently join on the same column.

Answer First: Hash partitioning = Take the key, run it through a math function, and the result tells you which partition. Like assigning students to classes by last-name hash. Fast, but no ordering.

Memory Map: Hash Partitioning vs Range Partitioning -> hash function spreads keys without preserving order -> ordered bounds keep adjacent values together -> equality workloads favor even distribution -> interval scans benefit from bounded placement [02_PySpark_Advanced_Operations.md:714].

Q12: Compare Hash Partitioning vs Range Partitioning.

Simple Explanation: Two ways to decide which partition a row goes to:

  • Hash partitioning = Take the key, run it through a math function, and the result tells you which partition. Like assigning students to classes by last-name hash. Fast, but no ordering.
  • Range partitioning = Split data into ranges (A-F, G-L, M-R, S-Z). Ordered, but requires knowing the data distribution first.

Technical details:

AspectHash PartitioningRange Partitioning
Algorithmpartition = hash(key) % numPartitionsPartitions by value ranges (requires sampling)
Use caseEqui-joins, groupByorderBy/sortBy, range queries
Skew riskYes, if hash distribution poor (e.g., many nulls)Can be balanced with good sampling
OutputUnordered within partitionsSorted partitions
python β€” editable
# Hash partitioning (implicit in repartition by column)
df.repartition(100, "user_id")
# ← partition = hash(user_id) % 100

# Range partitioning (implicit in orderBy/sortBy)
df.repartitionByRange(100, "date")
# ← partition 0 gets Jan, partition 1 gets Feb, etc.

Interview Tip: Hash partitioning is what Spark uses internally for shuffles during joins and groupBy. Range partitioning is what Spark uses for global sorting (orderBy).

What NOT to Say: "Hash partitioning always gives even distribution." If most of your keys hash to the same bucket (e.g., lots of NULLs), you get skew.

SECTION 4: UDFs & PERFORMANCE

Answer First: A UDF (User-Defined Function) is custom Python code that you plug into Spark. Spark runs on the JVM (Java Virtual Machine), but your Python code runs in a separate Python process.

Memory Map: UDFs? Why should you avoid them? What are the alternatives -> Python UDF crosses the JVM boundary -> serialization blocks Catalyst visibility -> built-ins or SQL expressions retain native optimization -> operator timing proves the replacement benefit [02_PySpark_Advanced_Operations.md:748].

Q13: What are UDFs? Why should you avoid them? What are the alternatives?

Simple Explanation: A UDF (User-Defined Function) is custom Python code that you plug into Spark. The problem? Spark runs on the JVM (Java Virtual Machine), but your Python code runs in a separate Python process. Every row of data has to be shipped from the JVM to Python and back β€” like sending mail between two buildings.

Analogy:

  • Native Spark functions = Walking inside one building (JVM). Everything is fast, the optimizer knows every step you take.
  • Python UDF = Sending mail between two buildings (JVM to Python and back). You have to package each letter (serialize), walk it to the other building (socket transfer), unpackage it (deserialize), process it, then package the result and walk it back. For EVERY. SINGLE. ROW.

Serialization diagram β€” why Python UDFs are slow:

πŸ“ Architecture Diagram
                    ROW-BY-ROW PYTHON UDF
                    =====================

JVM (Spark Executor)              Python Worker Process
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”              β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                  β”‚   serialize  β”‚                  β”‚
β”‚  Row 1 data  ────┼──────────►──┼──► process row 1 β”‚
β”‚                  β”‚   (pickle)  β”‚     (your code)   β”‚
β”‚  ◄───────────────┼──────────◄──┼──── result 1     β”‚
β”‚                  β”‚ deserialize β”‚                  β”‚
β”‚  Row 2 data  ────┼──────────►──┼──► process row 2 β”‚
β”‚                  β”‚             β”‚                  β”‚
β”‚  ◄───────────────┼──────────◄──┼──── result 2     β”‚
β”‚                  β”‚             β”‚                  β”‚
β”‚  ... Γ— millions  β”‚             β”‚  ... Γ— millions  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜              β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Overhead per row: serialize + socket transfer + deserialize + Python GIL
Total overhead: O(num_rows) Γ— per-row cost = VERY SLOW

                    PANDAS UDF (VECTORIZED)
                    =======================

JVM (Spark Executor)              Python Worker Process
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”              β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                  β”‚  Arrow batch β”‚                  β”‚
β”‚  10,000 rows ────┼──────────►──┼──► pd.Series     β”‚
β”‚  (one batch)     β”‚  (zero-copy)β”‚  (vectorized ops) β”‚
β”‚                  β”‚             β”‚                  β”‚
β”‚  ◄───────────────┼──────────◄──┼──── results batchβ”‚
β”‚                  β”‚             β”‚                  β”‚
β”‚  next 10K rows ──┼──────────►──┼──► pd.Series     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜              β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Overhead per batch: 1 Arrow transfer (near zero-copy)
Total overhead: O(num_batches) Γ— per-batch cost = MUCH FASTER

Technical details:

Why Python UDFs are slow:

  1. Data serialized from JVM β†’ Python process (via socket) β†’ back to JVM
  2. Each row individually processed in Python (no vectorization)
  3. Catalyst cannot optimize through UDFs (no predicate pushdown, no codegen)
  4. Python GIL limits true parallelism within a worker

Performance hierarchy (fastest to slowest):

  1. Built-in Spark SQL functions β€” Catalyst-optimized, codegen, runs in JVM (1x baseline)
  2. Pandas UDF (vectorized) β€” Arrow serialization, batch processing with pandas/numpy (3-5x slower)
  3. mapInPandas β€” Similar to Pandas UDF, for partition-level processing (5-10x slower)
  4. Row-at-a-time Python UDF β€” Avoid if possible (10-100x slower)

Interview Tip: If asked "How do you optimize a Python UDF?", say: "First, I try to replace it with built-in functions. If not possible, I convert it to a Pandas UDF for vectorized processing with Apache Arrow. As a last resort, I'd use mapInPandas for partition-level processing."

What NOT to Say: "UDFs are fine, they're just Python functions." This shows you don't understand the JVM-Python serialization overhead that makes them 10-100x slower.

Answer First: Pandas UDFs come in 3 flavors, depending on what goes IN and what comes OUT.

Memory Map: a Pandas UDF. When do you use SCALAR vs GROUPED_MAP vs GROUPED_AGG -> Arrow transfers vector batches into Python -> scalar form returns one value per input row -> grouped-map form returns a frame per group -> grouped aggregate returns one value per group [02_PySpark_Advanced_Operations.md:817].

Q14: Write a Pandas UDF. When do you use SCALAR vs GROUPED_MAP vs GROUPED_AGG?

Simple Explanation: Pandas UDFs come in 3 flavors, depending on what goes IN and what comes OUT:

  • Scalar = Column in β†’ Column out (like a regular Spark function but written in pandas)
  • Grouped Map = Group of rows in β†’ Group of rows out (for per-group processing like ML)
  • Grouped Aggregate = Group of rows in β†’ Single value out (for custom aggregations)

Technical details:

Scalar Pandas UDF (column β†’ column):

python β€” editable
from pyspark.sql.functions import pandas_udf
from pyspark.sql.types import DoubleType
import pandas as pd

@pandas_udf(DoubleType())
def normalize(s: pd.Series) -> pd.Series:
    return (s - s.mean()) / s.std()
    # ← Receives a batch of rows as pd.Series, returns pd.Series
    # ← Vectorized: numpy operations on the whole batch, not row-by-row

df = df.withColumn("normalized_salary", normalize(col("salary")))

Grouped Map (group β†’ DataFrame):

python β€” editable
from pyspark.sql.functions import pandas_udf, PandasUDFType

@pandas_udf(output_schema, PandasUDFType.GROUPED_MAP)
def train_model(pdf: pd.DataFrame) -> pd.DataFrame:
    # ← Receives ALL rows for one group as a pandas DataFrame
    model = LinearRegression().fit(pdf[features], pdf[target])
    pdf["prediction"] = model.predict(pdf[features])
    return pdf  # ← Returns a pandas DataFrame (can be different shape)

result = df.groupBy("category").apply(train_model)
# ← Trains a SEPARATE model per category β€” powerful for ML!

Grouped Aggregate (group β†’ scalar):

python β€” editable
@pandas_udf(DoubleType(), PandasUDFType.GROUPED_AGG)
def weighted_mean(values: pd.Series, weights: pd.Series) -> float:
    return (values * weights).sum() / weights.sum()
    # ← Custom aggregation that Spark doesn't have built-in

result = df.groupBy("category").agg(weighted_mean(col("value"), col("weight")))

When to use which:

🧠 Memory Map
Need to transform a column?β†’SCALAR
Example: normalize values, parse strings, custom math
Need to process entire groups?β†’GROUPED_MAP
Example: train ML model per group, custom time-series per group
Need a custom aggregate?β†’GROUPED_AGG
Example: weighted mean, trimmed mean, custom statistical functions

Interview Tip: In modern PySpark (3.0+), the decorator syntax is preferred. Mention that Scalar Pandas UDFs are the most common and give the best performance improvement over regular UDFs (3-100x faster due to Arrow serialization).

What NOT to Say: "I'd write a regular Python UDF instead." Always prefer Pandas UDFs when you must use custom Python logic.

Answer First: mapInPandas lets you process entire partitions of data using pandas. Instead of getting one column or one group, you get an iterator of pandas DataFrames β€” each representing a chunk of the partition. This is perfect for "load a model once, score every row" patterns.

Memory Map: mapInPandas and when do you use it -> each partition becomes an iterator of pandas frames -> Python code may emit zero or many frames -> schema contract validates returned batches -> worker memory bounds safe partition size [02_PySpark_Advanced_Operations.md:886].

Q15: What is mapInPandas and when do you use it?

Simple Explanation: mapInPandas lets you process entire partitions of data using pandas. Instead of getting one column or one group, you get an iterator of pandas DataFrames β€” each representing a chunk of the partition. This is perfect for "load a model once, score every row" patterns.

Analogy: Instead of sending each letter individually (row-at-a-time UDF) or even in batches (Pandas UDF), you send the entire mailbag to the Python worker and let it process everything at once. The model is loaded ONCE per partition, not per row or per batch.

Technical details:

python β€” editable
def predict_batch(iterator):
    import pickle
    model = pickle.load(open("/dbfs/models/model.pkl", "rb"))
    # ← Model loaded ONCE per partition (not per row!)
    for batch_df in iterator:
        # ← Each batch_df is a pandas DataFrame chunk
        batch_df["prediction"] = model.predict(batch_df[feature_cols])
        yield batch_df  # ← Yield results back as pandas DataFrame

result = spark_df.mapInPandas(predict_batch, schema=output_schema)
# ← Each partition: load model once β†’ score all rows β†’ return results

Use when:

  • You need custom Python logic that can't be expressed with built-in functions
  • Processing entire partitions (load model once, apply to all rows)
  • Need pandas/numpy for complex computations
  • You want to avoid the overhead of loading resources per batch

Interview Tip: Mention that mapInPandas uses Apache Arrow for zero-copy transfer between JVM and Python, making it much faster than traditional mapPartitions with manual serialization.

What NOT to Say: "I'd use a regular UDF to load the model and score each row." Loading a model per row is catastrophically slow.

SECTION 5: CACHING & CHECKPOINTING

Answer First: When you reuse a DataFrame multiple times, Spark normally recomputes it from scratch each time (lazy evaluation). Caching, persisting, and checkpointing are ways to say "save this result so we don't have to recompute it.".

Memory Map: cache(), persist(), and checkpoint(). When would you use each -> cache persist and checkpoint would use each sets the durability and recomputation trade-off -> reused computation identifies materialization value -> storage level or checkpoint sets durability -> partitions are materialized -> reuse and eviction metrics justify the choice [02_PySpark_Advanced_Operations.md:923].

Q16: Explain cache(), persist(), and checkpoint(). When would you use each?

Simple Explanation: When you reuse a DataFrame multiple times, Spark normally recomputes it from scratch each time (lazy evaluation). Caching, persisting, and checkpointing are ways to say "save this result so we don't have to recompute it."

Analogy:

  • cache() = Bookmarking a page in a book. Quick to find again, but if the book gets damaged (executor fails), you lose the bookmark and must search again.
  • persist() = Same as cache, but you can choose WHERE to save (memory, disk, or both). Like choosing between a sticky note (memory), a physical bookmark (disk), or both.
  • checkpoint() = Photocopying the page and storing the copy in a safe (HDFS/S3). Even if the original book is destroyed, you still have the copy. Also, you don't need to remember which chapter you were in β€” the copy is standalone.

Technical details:

MethodStorageLineageFault TolerantUse Case
cache()MEMORY_AND_DISKPreservedNo (recompute)Reused DataFrame
persist(MEMORY_ONLY)Memory onlyPreservedNoFits in memory, reused often
persist(DISK_ONLY)Disk onlyPreservedNoLarge data, infrequent reuse
persist(MEMORY_AND_DISK_SER)Memory (serialized) + diskPreservedNoMemory-constrained
checkpoint()Reliable storage (HDFS/S3)TruncatedYesLong lineage, iterative algorithms
localCheckpoint()Executor local storageTruncatedNoFast lineage break, less reliable

When to checkpoint vs cache:

  • Use cache() when the DataFrame is reused 2+ times and you want to avoid recomputation
  • Use checkpoint() when the lineage is very deep (iterative algorithms) to prevent StackOverflow
  • Always call an action after checkpoint to materialize: df.checkpoint(); df.count()
python β€” editable
# Common pattern: cache a filtered+transformed DataFrame used multiple times
filtered_df = raw_df.filter(col("status") == "active") \
    .select("user_id", "email", "created_at")
filtered_df.cache()       # ← mark for caching
filtered_df.count()       # ← trigger materialization (lazy!)

# Now these two operations read from cache, not from raw data:
filtered_df.groupBy("created_at").count().show()
filtered_df.join(other_df, "user_id").show()

# Don't forget to unpersist when done!
filtered_df.unpersist()   # ← free the memory

Interview Tip: Always mention unpersist(). Forgetting to release cached DataFrames is a common memory leak in production jobs. Also mention that cache() is lazy β€” the data isn't actually cached until an action triggers it.

What NOT to Say: "I cache everything to make it faster." Over-caching wastes memory and can cause executors to spill to disk, making things SLOWER.

SECTION 6: STRUCTURED STREAMING

Answer First: Structured Streaming models an input stream as an incrementally updated table and executes batch-style transformations on each new range of data. Checkpoints preserve source progress and state across restarts.

Memory Map: the Structured Streaming execution model -> structured streaming execution model controls one checkpointed streaming state transition -> offset and event time enter the micro-batch -> stateful logic applies the watermark boundary -> checkpoint commits progress -> replay and sink checks prove correctness [02_PySpark_Advanced_Operations.md:972].

Q17: Explain the Structured Streaming execution model.

Simple Explanation: Structured Streaming treats real-time data as a table that keeps growing. Every few seconds (or whatever interval you set), Spark looks at "what new rows arrived?" and processes just those new rows using the exact same code you'd write for a batch query.

Analogy: Imagine a restaurant where orders come in continuously. Instead of waiting until the restaurant closes to count all orders, the manager checks the order list every 10 seconds and processes only the NEW orders since the last check. Same counting method, just applied incrementally.

Technical details:

  • The stream is treated as an unbounded table
  • Each trigger processes new rows appended to this table
  • Uses the same Catalyst optimizer as batch queries

Trigger modes:

ModeBehavior
trigger(processingTime="10 seconds")Micro-batch every 10 seconds
trigger(once=True)Process all available, stop (deprecated)
trigger(availableNow=True)Process all available in multiple micro-batches, stop
Continuous (experimental)Row-by-row, ~1 ms latency, at-least-once only
python β€” editable
# Basic streaming pipeline structure
result = spark.readStream \
    .format("delta") \
    .table("bronze_events") \
    .filter(col("event_type") == "purchase") \
    .groupBy("product_id").count()

result.writeStream \
    .format("delta") \
    .outputMode("complete") \
    .trigger(processingTime="10 seconds") \
    .option("checkpointLocation", "/checkpoints/purchase_counts") \
    .toTable("silver_purchase_counts")
# ← Every 10 seconds: read new rows β†’ filter β†’ aggregate β†’ write

Interview Tip: Emphasize that the code is nearly identical to batch β€” "I can prototype in batch, then switch to streaming by changing read to readStream and write to writeStream." This is Structured Streaming's key design principle.

What NOT to Say: "Structured Streaming processes one row at a time." It uses micro-batches (unless using the experimental continuous mode).

Answer First: Append mode emits only finalized new rows, update mode emits rows changed in the batch, and complete mode rewrites the entire result table. Query shape and sink capability determine which modes are valid.

Memory Map: output modes? When is each used -> result mutability defines the sink contract -> immutable completions permit incremental emission -> changing aggregates require replacement records -> full-state sinks accept snapshot rewrites [02_PySpark_Advanced_Operations.md:1016].

Q18: What are output modes? When is each used?

Simple Explanation: After each micro-batch, Spark needs to know: "What results should I write to the sink?" The three output modes answer this differently:

  • Append = "Only write NEW rows that won't change" (like adding new entries to a log)
  • Complete = "Rewrite the ENTIRE result table every time" (like refreshing a dashboard)
  • Update = "Only write rows that CHANGED" (like updating a leaderboard)

Technical details:

ModeBehaviorWorks With
Append (default)Only new rows outputNon-aggregation queries, or aggregations with watermark
CompleteEntire result table outputOnly with aggregations
UpdateOnly changed rows outputAggregations (rows whose aggregate value changed)
🧠 Memory Map
Example: counting events per category
After batch 1: {Electronics: 10, Books: 5}
After batch 2: {Electronics: 15, Books: 5, Clothing: 3}
Append mode: Would write→{Clothing: 3} (only truly new categories)
ERROR! Can't guarantee Electronics won't change.
Complete mode: Would write→{Electronics: 15, Books: 5, Clothing: 3}
Entire result table every time. Safe but expensive.
Update mode: Would write→{Electronics: 15, Clothing: 3}
Only the rows that changed. Books stayed at 5 so it's skipped.

Common mistake: Using append mode with aggregations without watermark β†’ throws error because Spark can't guarantee old rows won't change.

Interview Tip: For most streaming-to-Delta pipelines without aggregation, use append. For aggregation dashboards, use update (more efficient than complete). Only use complete when the sink needs the full picture every time.

What NOT to Say: "I always use complete mode." Complete rewrites the entire result every micro-batch β€” extremely inefficient for large state.

Answer First: A click that happened at 3:00 PM might only arrive at the server at 3:25 PM (due to network delays, offline devices, etc.). Watermarking tells Spark: "Wait up to X minutes for late data.

Memory Map: watermarking with a real scenario -> event timestamps advance the maximum observed time -> allowed delay derives an eviction boundary -> older state can be removed -> records beyond that boundary follow late-data semantics [02_PySpark_Advanced_Operations.md:1057].

Q19: Explain watermarking with a real scenario.

Simple Explanation: In the real world, data arrives late. A click that happened at 3:00 PM might only arrive at the server at 3:25 PM (due to network delays, offline devices, etc.). Watermarking tells Spark: "Wait up to X minutes for late data. After that, stop waiting and clean up."

Analogy: A professor has a homework deadline of Monday 5 PM. But they accept late submissions up to 30 minutes (the watermark). At 5:30 PM, they stop accepting papers and grade what they have. Without this cutoff, they'd wait forever and never grade anything.

Technical details:

Scenario: Clickstream sessionization. Events may arrive up to 30 minutes late.

python β€” editable
from pyspark.sql.functions import window

clicks = spark.readStream.format("kafka").load().select(
    col("user_id"),
    col("event_time").cast("timestamp"),
    col("page_url")
)

# Define watermark: accept data up to 30 min late
sessionized = clicks \
    .withWatermark("event_time", "30 minutes") \
    .groupBy(
        col("user_id"),
        window("event_time", "1 hour")  # ← 1-hour tumbling window
    ).count()

What watermark does β€” step by step:

🧠 Memory Map
Time progresses:
3:00 PM β€” events arrive with event_time = 3:00 PM
max(event_time) = 3:00 PM
watermark = 3:00 - 30 min = 2:30 PM
β†’ Accept any event with event_time >= 2:30 PM
3:15 PM β€” events arrive with event_time = 3:15 PM
max(event_time) = 3:15 PM
watermark = 3:15 - 30 min = 2:45 PM
β†’ Accept any event with event_time >= 2:45 PM
β†’ Late event with event_time = 2:40 PM? DROPPED! (before watermark)
3:30 PM β€” max(event_time) = 3:30 PM
watermark = 3:00 PM
β†’ State for windows ending before 3:00 PM is CLEANED UP
β†’ This prevents unbounded state growth!

What watermark does (summary):

  1. Tracks max(event_time) seen so far
  2. watermark = max(event_time) - 30 minutes
  3. Events with event_time < watermark are dropped
  4. State older than watermark is cleaned up (prevents unbounded state growth)

Interview Tip: Always connect watermarks to STATE CLEANUP. The interviewer wants to hear: "Without watermarks, the state store grows forever. Watermarks let Spark know when it's safe to discard old state."

What NOT to Say: "Watermarks guarantee no data is ever lost." Late data arriving AFTER the watermark IS dropped. It's a trade-off between completeness and resource usage.

Answer First: Joining two streams is like matching real-time orders with real-time payments. Both arrive continuously, and you need to hold onto unmatched records from both sides until a match arrives (or until you give up waiting).

Memory Map: stream-stream joins work? What are the requirements -> both inputs require event-time columns -> watermarks bound retained state on each side -> time-range condition limits possible matches -> state metrics reveal whether bounds are effective [02_PySpark_Advanced_Operations.md:1118].

Q20: How do stream-stream joins work? What are the requirements?

Simple Explanation: Joining two streams is like matching real-time orders with real-time payments. Both arrive continuously, and you need to hold onto unmatched records from both sides until a match arrives (or until you give up waiting).

Technical details:

python β€” editable
# Both streams must have watermarks
orders = orders_stream.withWatermark("order_time", "2 hours")
# ← "Orders can arrive up to 2 hours late"
payments = payments_stream.withWatermark("payment_time", "3 hours")
# ← "Payments can arrive up to 3 hours late"

# Time-range condition limits state
joined = orders.join(
    payments,
    expr("""
        orders.order_id = payments.order_id AND
        payments.payment_time BETWEEN orders.order_time AND orders.order_time + interval 1 hour
    """),
    "left_outer"
)
# ← "Payment must arrive within 1 hour of order time"

Requirements:

  1. Both sides must have watermarks defined
  2. Time-range conditions recommended to limit state
  3. For outer joins: a row is output with nulls once the watermark guarantees no future match is possible
  4. For inner joins: late data on either side is buffered until watermark allows cleanup
🧠 Memory Map
Without time-range condition:
Spark must buffer ALL unmatched orders and ALL unmatched payments FOREVER
→ State grows unbounded→OOM
With time-range condition (payment within 1 hour of order):
Spark knows: if order_time = 3:00 PM and it's now 4:00 PM,
no payment can possibly match→safe to discard that order from state
→ State is bounded→stable memory usage

Interview Tip: Stream-stream joins are a favorite advanced topic. Mention the state implications: without time bounds and watermarks, state grows forever and the job eventually OOMs.

What NOT to Say: "Stream-stream joins work just like batch joins." They require watermarks and time-range conditions that batch joins don't need.

Answer First: foreachBatch is a bridge between streaming and batch. It says: "For each micro-batch, give me the data as a regular DataFrame β€” then I'll decide what to do with it." This unlocks batch-only operations (like MERGE) inside a streaming pipeline.

Memory Map: the foreachBatch pattern. When is it needed -> engine hands each micro-batch DataFrame and ID to user code -> batch APIs perform unsupported sink or merge logic -> transaction key makes replay idempotent -> checkpoint restart proves safe re-execution [02_PySpark_Advanced_Operations.md:1167].

Q21: Explain the foreachBatch pattern. When is it needed?

Simple Explanation: foreachBatch is a bridge between streaming and batch. It says: "For each micro-batch, give me the data as a regular DataFrame β€” then I'll decide what to do with it." This unlocks batch-only operations (like MERGE) inside a streaming pipeline.

Analogy: Imagine a conveyor belt (the stream) dropping boxes onto a table every 30 seconds. foreachBatch lets you pick up each batch of boxes and do whatever you want with them β€” sort them, compare with existing inventory, ship some back β€” things you can't do while they're on the moving belt.

Technical details:

python β€” editable
def upsert_to_delta(batch_df, batch_id):
    # ← batch_df: regular DataFrame with this micro-batch's data
    # ← batch_id: unique ID for this batch (for idempotency)
    target = DeltaTable.forName(spark, "silver_orders")

    target.alias("t").merge(
        batch_df.alias("s"),
        "t.order_id = s.order_id"
    ).whenMatchedUpdateAll() \
     .whenNotMatchedInsertAll() \
     .execute()

spark.readStream.table("bronze_orders") \
    .writeStream \
    .foreachBatch(upsert_to_delta) \
    .option("checkpointLocation", "/checkpoints/silver_orders") \
    .trigger(processingTime="1 minute") \
    .start()

Use when:

  • MERGE into Delta Lake (can't do with regular streaming write)
  • Writing to multiple sinks in one pipeline
  • Calling external APIs per batch
  • Complex deduplication logic
  • Any operation that needs the full batch as a DataFrame

Interview Tip: foreachBatch is the answer to "How do you do streaming MERGE/upsert in Databricks?" β€” the most common streaming interview question. Always mention using batch_id for idempotency.

What NOT to Say: "I'd stop the stream, run a batch MERGE, then restart." That defeats the purpose of streaming.

Answer First: "Exactly-once" means every record is processed exactly one time β€” not zero, not twice. This is surprisingly hard in distributed systems. Spark achieves it by combining three things: a replayable source, checkpointing, and an idempotent sink.

Memory Map: you achieve exactly-once semantics in Structured Streaming -> source offsets persist in checkpoint state -> deterministic processing recreates a batch -> idempotent or transactional sink rejects duplicate commit IDs -> restart reconciliation proves one visible result [02_PySpark_Advanced_Operations.md:1210].

Q22: How do you achieve exactly-once semantics in Structured Streaming?

Simple Explanation: "Exactly-once" means every record is processed exactly one time β€” not zero, not twice. This is surprisingly hard in distributed systems. Spark achieves it by combining three things: a replayable source, checkpointing, and an idempotent sink.

Analogy: Imagine processing bank transactions:

  1. Source must be replayable β€” like a numbered list where you can say "start from transaction #500" (Kafka offsets)
  2. Engine must remember where it stopped β€” "I finished up to transaction #499" (checkpointing)
  3. Sink must handle duplicates β€” "If I accidentally process #499 again, the result is the same" (idempotent writes)

Technical details:

Three requirements:

  1. Source: Must be replayable (Kafka with offsets, file source with checkpoints)
  2. Engine: Checkpointing tracks offsets and state. On restart, Spark replays from last committed offset.
  3. Sink: Must be idempotent (re-writing the same batch produces the same result)

Built-in exactly-once sinks:

  • Delta Lake (ACID transactions)
  • File sink (uses batch ID in file names)
  • Kafka sink (with idempotent producer)
python β€” editable
def idempotent_write(batch_df, batch_id):
    # ← Use batch_id to ensure idempotency
    # ← If this batch is replayed, the overwrite produces the same result
    batch_df.write.format("delta") \
        .mode("overwrite") \
        .option("replaceWhere", f"batch_id = {batch_id}") \
        .save("/path/to/output")

Interview Tip: The key insight is that exactly-once is achieved END-TO-END, not by any single component. Source + Engine + Sink must all cooperate. If any one fails (e.g., a non-idempotent sink), you lose the guarantee.

What NOT to Say: "Spark guarantees exactly-once automatically." Only true with the right source + sink combination. A write to a REST API without idempotency keys is NOT exactly-once.

Answer First: The state store holds intermediate data that the streaming query needs to remember (e.g., running counts, unmatched join records, dedup history). If it grows forever, your job will eventually run out of memory and crash.

Memory Map: Scenario β€” Your streaming pipeline's state store is growing unbounded. How do you fix it -> stateful keys accumulate without an eviction condition -> event-time bounds make old entries removable -> aggregation or join logic applies the boundary -> state-row metrics confirm bounded growth [02_PySpark_Advanced_Operations.md:1249].

Q23: Scenario β€” Your streaming pipeline's state store is growing unbounded. How do you fix it?

Simple Explanation: The state store holds intermediate data that the streaming query needs to remember (e.g., running counts, unmatched join records, dedup history). If it grows forever, your job will eventually run out of memory and crash.

This is like a to-do list that only adds items but never removes completed ones β€” eventually the list is so long you can't carry it.

Technical details:

  1. Add watermarks to bound the state
  2. Add time constraints on joins to limit buffered data
  3. Use RocksDB state store (disk-based, handles large state):
python β€” editable
spark.conf.set(
    "spark.sql.streaming.stateStore.providerClass",
    "org.apache.spark.sql.execution.streaming.state.RocksDBStateStoreProvider"
)
# ← Default state store is in-memory (HashMap). RocksDB uses disk + memory cache
# ← Can handle GBs of state that would OOM with the default store
  1. Set spark.sql.streaming.stateStore.minDeltasForSnapshot for compaction
  2. Monitor state via StreamingQueryListener:
python β€” editable
class StateMonitor(StreamingQueryListener):
    def onQueryProgress(self, event):
        state_info = event.progress.stateOperators
        for op in state_info:
            print(f"State rows: {op.numRowsTotal}, Memory: {op.memoryUsedBytes}")
            # ← Watch these numbers. If numRowsTotal only goes UP, you have a problem

Checklist for debugging unbounded state:

πŸ—‚οΈState growing unbounded?
Are watermarks defined?
NO β†’ Add withWatermark() on the event-time column
YES β†’ Is the watermark delay too large? Reduce it.
Are join conditions time-bounded?
NO β†’ Add "BETWEEN order_time AND order_time + interval X"
YES β†’ Is the interval too large?
Are you using dropDuplicates without watermark?
YES β†’ Add watermark so Spark can expire old dedup state
Is the state store in-memory?
YES β†’ Switch to RocksDB state store for disk-based state

Interview Tip: Start with watermarks (the most common fix), then mention RocksDB (shows deep knowledge). If the interviewer asks about monitoring, mention StreamingQueryListener.

What NOT to Say: "I'd just increase executor memory." That's a band-aid, not a fix. The state will keep growing and eventually exceed any amount of memory.

Answer First: Both are for "batch-style streaming" β€” process all available data, then stop. The difference is HOW they process.

Memory Map: the difference between trigger(once=True) and trigger(availableNow=True) -> once trigger processes one micro-batch then stops -> available-now trigger advances through all pending batches -> checkpoint preserves progress between batches -> backlog tests expose semantic difference [02_PySpark_Advanced_Operations.md:1301].

Q24: What is the difference between trigger(once=True) and trigger(availableNow=True)?

Simple Explanation: Both are for "batch-style streaming" β€” process all available data, then stop. The difference is HOW they process:

  • trigger(once=True) = Shoves ALL data into ONE micro-batch. Can OOM if there's a lot of data.
  • trigger(availableNow=True) = Splits data into MULTIPLE micro-batches, processes them sequentially, then stops. Memory-friendly.

Analogy: You have 1000 emails to process. once=True = try to open all 1000 at once (your computer might crash). availableNow=True = open 100 at a time, 10 batches, then stop (much safer).

Technical details:

Aspecttrigger(once=True)trigger(availableNow=True)
ProcessingOne single micro-batchMultiple micro-batches
ParallelismAll data in one batchSpreads across multiple batches
MemoryCan OOM on large backlogMore memory-friendly
StatusDeprecated (Spark 3.3+)Recommended replacement
Use casePeriodic batch-style runsPeriodic batch-style runs (better)
python β€” editable
# Old way (deprecated):
df.writeStream.trigger(once=True).start()
# ← Processes ALL available data in a single micro-batch
# ← If 100 GB arrived since last run β†’ tries to process 100 GB at once β†’ OOM

# New way (recommended):
df.writeStream.trigger(availableNow=True).start()
# ← Processes all available data in multiple micro-batches
# ← 100 GB arrives β†’ splits into 10 x 10 GB batches β†’ processes sequentially β†’ safe

Interview Tip: Always recommend availableNow=True over once=True. It shows you know the modern API and understand the memory implications. Common pattern: schedule this via Databricks Jobs to run every hour for cost-efficient near-real-time processing.

What NOT to Say: "They're the same thing." The memory behavior difference is significant for production workloads.

SECTION 7: CODING CHALLENGES

Answer First: You have duplicate records for the same ID (e.g., multiple updates to the same user). You want to keep only the LATEST version of each record. This is the most common PySpark interview coding question.

Memory Map: Deduplicate records keeping the most recent per key -> business key partitions duplicate candidates -> descending event order ranks newest rows -> row-number filter retains one record -> uniqueness and freshness assertions confirm output [02_PySpark_Advanced_Operations.md:1341].

Q25: Deduplicate records keeping the most recent per key.

Simple Explanation: You have duplicate records for the same ID (e.g., multiple updates to the same user). You want to keep only the LATEST version of each record. This is the most common PySpark interview coding question.

Technical details:

python β€” editable
from pyspark.sql.functions import row_number, col
from pyspark.sql import Window

window = Window.partitionBy("id").orderBy(col("updated_at").desc())
# ← "Within each id, sort by updated_at with newest first"

deduped = df.withColumn("rn", row_number().over(window)) \
    .filter(col("rn") == 1) \
    .drop("rn")
# ← row_number gives 1 to the newest, 2 to the second newest, etc.
# ← Filter rn == 1 keeps ONLY the newest record per id

Sample data flow:

idnameupdated_at
1Alice2026-01-01
1Alice_v22026-01-15
2Bob2026-01-10
idnameupdated_at
1Alice_v22026-01-15
1Alice2026-01-01
2Bob2026-01-10
idnameupdated_at
1Alice_v22026-01-15
2Bob2026-01-10

Interview Tip: Use row_number(), NOT rank() or dense_rank(). Those can give ties, meaning you'd keep multiple rows per key. row_number() guarantees exactly one row per partition.

What NOT to Say: "I'd use dropDuplicates(['id'])." That keeps an ARBITRARY record, not necessarily the most recent one. You can't control which record is kept.

Answer First: Pivoting turns unique values in a column into separate columns. Like turning a vertical list of sales-by-category into a horizontal spreadsheet with one column per category.

Memory Map: Pivot a table β€” convert rows to columns -> group keys establish output rows -> pivot values become named columns -> aggregate resolves collisions within each cell -> expected schema and totals validate reshaping [02_PySpark_Advanced_Operations.md:1391].

Q26: Pivot a table β€” convert rows to columns.

Simple Explanation: Pivoting turns unique values in a column into separate columns. Like turning a vertical list of sales-by-category into a horizontal spreadsheet with one column per category.

Technical details:

python β€” editable
# Always pass distinct values list to avoid an extra job
pivoted = df.groupBy("date") \
    .pivot("category", ["Electronics", "Clothing", "Food"]) \
    .agg(sum("amount"))
# ← Pass the list ["Electronics", "Clothing", "Food"] explicitly!
# ← Without it, Spark runs an extra job to discover all distinct values

Sample data flow:

datecategoryamount
2026-01-01Electronics100
2026-01-01Clothing50
2026-01-01Food30
2026-01-02Electronics120
2026-01-02Food40
dateElectronicsClothing
2026-01-0110050
2026-01-02120NULL

Follow-up: How do you unpivot (melt)?

python β€” editable
from pyspark.sql.functions import expr

# Spark 3.4+ native unpivot:
unpivoted = df.unpivot("date", ["Electronics", "Clothing", "Food"], "category", "amount")

# Before 3.4 β€” use stack:
unpivoted = df.selectExpr(
    "date",
    "stack(3, 'Electronics', Electronics, 'Clothing', Clothing, 'Food', Food) as (category, amount)"
)
# ← stack(N, ...) takes N key-value pairs and turns columns back into rows

Interview Tip: Always pass the explicit list of values to pivot(). Without it, Spark scans the entire dataset first to discover distinct values β€” an expensive extra job.

What NOT to Say: "I'd use a for-loop to create separate DataFrames and union them." That's the anti-pattern that pivot was designed to replace.

Answer First: Given a series of IDs (1, 2, 3, 5, 6, 10), find where the gaps are (4 is missing, 7-8-9 are missing). This is useful for audit logs, sequence validation, and data quality checks.

Memory Map: Find gaps in a sequential series -> ordered sequence pairs each value with predecessor -> lag exposes expected next value -> non-unit differences mark missing intervals -> boundary expansion lists actual gaps [02_PySpark_Advanced_Operations.md:1446].

Q27: Find gaps in a sequential series.

Simple Explanation: Given a series of IDs (1, 2, 3, 5, 6, 10), find where the gaps are (4 is missing, 7-8-9 are missing). This is useful for audit logs, sequence validation, and data quality checks.

Technical details:

python β€” editable
from pyspark.sql.functions import lead
from pyspark.sql import Window

w = Window.orderBy("sequence_id")
gaps = df.withColumn("next_id", lead("sequence_id").over(w)) \
    .filter(col("next_id") - col("sequence_id") > 1) \
    .select(
        col("sequence_id").alias("gap_start"),
        col("next_id").alias("gap_end"),
        (col("next_id") - col("sequence_id") - 1).alias("gap_size")
    )
# ← lead() looks at the NEXT row's value
# ← If current=3 and next=5, the gap is 4 (size 1)

Sample data flow:

5← gap: 4 is missing
10← gap: 7,8,9 are missing
sequence_idnext_id
12
23
35
56
610
10NULL
gap_startgap_end
35
610

Interview Tip: This pattern (lead/lag + filter) is the standard approach for detecting gaps, islands, and consecutive sequences. Master it β€” it appears in many variations.

What NOT to Say: "I'd generate all numbers and do a left anti join." That works but is wasteful for sparse sequences with large ranges.

Answer First: Sessionization groups user clicks into "sessions." A new session starts when the user has been inactive for more than X minutes (e.g., 30 minutes). If Alice clicks at 1:00, 1:05, 1:10, then 3:00 β€” the first three clicks are session 1, and 3:00 starts session 2.

Memory Map: Sessionize clickstream data (gap-based sessions) -> user events sort by occurrence time -> lag measures inactivity between clicks -> threshold crossings start new sessions -> cumulative sum assigns stable session identifiers [02_PySpark_Advanced_Operations.md:1504].

Q28: Sessionize clickstream data (gap-based sessions).

Simple Explanation: Sessionization groups user clicks into "sessions." A new session starts when the user has been inactive for more than X minutes (e.g., 30 minutes). If Alice clicks at 1:00, 1:05, 1:10, then 3:00 β€” the first three clicks are session 1, and 3:00 starts session 2.

Technical details:

python β€” editable
from pyspark.sql.functions import lag, when, unix_timestamp, sum as _sum, monotonically_increasing_id
from pyspark.sql import Window

w = Window.partitionBy("user_id").orderBy("event_time")
session_timeout = 30 * 60  # ← 30 minutes in seconds

df = df.withColumn("prev_time", lag("event_time").over(w)) \
    .withColumn("new_session",
        when(
            (unix_timestamp("event_time") - unix_timestamp("prev_time")) > session_timeout, 1
        ).when(col("prev_time").isNull(), 1)  # ← first event is always new session
        .otherwise(0)
    ) \
    .withColumn("session_id",
        _sum("new_session").over(w.rowsBetween(Window.unboundedPreceding, Window.currentRow))
    )
# ← Running sum of new_session flags gives each session a unique incrementing ID

Sample data flow:

πŸ“‹ Overview
INPUT (user Alice)
| user_id | event_time | page |
|---------|-----------|-----------|
| Alice | 1:00 PM | /home |
| Alice | 1:05 PM | /products |
| Alice | 1:10 PM | /cart |
| Alice | 3:00 PM | /home | ← 110 min gap > 30 min timeout
| Alice | 3:02 PM | /checkout |
STEP 1 β€” lag() gets previous event time:
| event_time | prev_time | gap_minutes |
|-----------|-----------|-------------|
| 1:00 PM | NULL | N/A |
| 1:05 PM | 1:00 PM | 5 |
| 1:10 PM | 1:05 PM | 5 |
| 3:00 PM | 1:10 PM | 110 | ← > 30 min!
| 3:02 PM | 3:00 PM | 2 |
STEP 2 β€” flag new sessions:
| event_time | new_session |
|-----------|-------------|
| 1:00 PM | 1 | ← first event = new session
| 1:05 PM | 0 | ← 5 min gap < 30 min
| 1:10 PM | 0 |
| 3:00 PM | 1 | ← 110 min gap > 30 min = NEW SESSION
| 3:02 PM | 0 |
STEP 3 β€” running sum gives session IDs:
| event_time | new_session | session_id |
|-----------|-------------|------------|
| 1:00 PM | 1 | 1 |
| 1:05 PM | 0 | 1 |
| 1:10 PM | 0 | 1 |
| 3:00 PM | 1 | 2 | ← new session!
| 3:02 PM | 0 | 2 |

Interview Tip: The key insight is using a running sum over boolean flags. This "cumulative sum of flags" pattern appears in many problems beyond sessionization (islands-and-gaps, state changes, etc.).

What NOT to Say: "I'd use a UDF to loop through rows and assign session IDs." Window functions do this natively and much faster.

Answer First: JSON data often has nested objects and arrays. "Flattening" means turning the nested structure into a simple table with one row per leaf-level record. Each explode() turns an array into multiple rows.

Memory Map: Flatten a deeply nested JSON structure -> schema inspection locates struct and array nesting -> struct expansion projects child fields -> explode creates rows for array elements -> row counts and null cases validate flattening [02_PySpark_Advanced_Operations.md:1576].

Q29: Flatten a deeply nested JSON structure.

Simple Explanation: JSON data often has nested objects and arrays. "Flattening" means turning the nested structure into a simple table with one row per leaf-level record. Each explode() turns an array into multiple rows.

Technical details:

python β€” editable
from pyspark.sql.functions import explode, col

raw = spark.read.json("/path/to/nested.json")

# Assume structure: {id, orders: [{order_id, amount, items: [{product, qty}]}]}
flat = raw.select("id", explode("orders").alias("order")) \
    .select(
        "id",
        col("order.order_id"),     # ← access struct fields with dot notation
        col("order.amount"),
        explode("order.items").alias("item")  # ← explode nested array
    ).select(
        "id",
        "order_id",
        "amount",
        col("item.product").alias("product_name"),
        col("item.qty").alias("quantity")
    )

Sample data flow:

idorder.order_idorder.amountorder.items
C1O1100[{Laptop,1},{Mouse,2}]
C1O250[{Book,3}]
idorder_idamountproduct_name
C1O1100Laptop
C1O1100Mouse
C1O250Book

Generic recursive flattener:

python β€” editable
from pyspark.sql.types import StructType, ArrayType

def flatten_df(df):
    """Recursively flatten all nested structs and arrays."""
    flat_cols = []
    for field in df.schema.fields:
        if isinstance(field.dataType, StructType):
            for subfield in field.dataType.fields:
                flat_cols.append(col(f"{field.name}.{subfield.name}").alias(f"{field.name}_{subfield.name}"))
        elif isinstance(field.dataType, ArrayType):
            df = df.withColumn(field.name, explode(col(field.name)))
            return flatten_df(df)  # ← Recurse after explode
        else:
            flat_cols.append(col(field.name))
    return df.select(flat_cols)

Interview Tip: Mention that explode creates a new row per array element, which multiplies the row count. For large arrays, this can cause data explosion. Consider using posexplode if you need the array index too.

What NOT to Say: "I'd convert to pandas and flatten there." That defeats the purpose of distributed processing and will fail on large datasets.

Answer First: Classic "top-N per group" pattern. First aggregate revenue per product, then rank within each category, then filter to keep only the top 3.

Memory Map: a query to find the top 3 products by revenue in each category -> line-item amounts aggregate by item and group -> window ordering sorts totals within each group -> dense rank preserves tied leaders -> position filter retains the first three ranks [02_PySpark_Advanced_Operations.md:1659].

Q30: Write a query to find the top 3 products by revenue in each category.

Simple Explanation: Classic "top-N per group" pattern. First aggregate revenue per product, then rank within each category, then filter to keep only the top 3.

Technical details:

python β€” editable
from pyspark.sql.functions import dense_rank, col, sum as _sum
from pyspark.sql import Window

# Step 1: Aggregate revenue per product per category
product_revenue = df.groupBy("category", "product_id") \
    .agg(_sum("revenue").alias("total_revenue"))

# Step 2: Rank within each category
w = Window.partitionBy("category").orderBy(col("total_revenue").desc())
top3 = product_revenue.withColumn("rnk", dense_rank().over(w)) \
    .filter(col("rnk") <= 3) \
    .drop("rnk")

Sample data flow:

categoryproduct_idtotal_revenue
ElectronicsP15000
ElectronicsP23000
ElectronicsP33000
ElectronicsP41000
BooksP52000
BooksP61500
categoryproduct_idtotal_revenue
ElectronicsP15000
ElectronicsP23000
ElectronicsP33000
ElectronicsP41000
BooksP52000
BooksP61500

Interview Tip: Use dense_rank() if you want ties included (both P2 and P3 at rank 2). Use row_number() if you want exactly 3 rows per category regardless of ties. Clarify with the interviewer which behavior they want.

What NOT to Say: "I'd sort each group and take the first 3." Without a window function, you'd need expensive groupBy + collect_list + slice patterns.

Answer First: Within each entity, order observations by time and use lag to retrieve the preceding value. Subtracting it from the current value yields the consecutive change while the first row remains null.

Memory Map: Compute the running difference between consecutive rows -> entity partition orders observations -> lag retrieves the prior value -> subtraction produces consecutive change -> first-row null handling preserves sequence semantics [02_PySpark_Advanced_Operations.md:1712].

Q31: Compute the running difference between consecutive rows.

Simple Explanation: For time-series data (sensor readings, stock prices), you often want to know: "How much did the value change from the previous reading?" Use lag() to look back one row.

Technical details:

python β€” editable
from pyspark.sql.functions import lag, col
from pyspark.sql import Window

w = Window.partitionBy("sensor_id").orderBy("timestamp")
result = df.withColumn("prev_value", lag("value", 1).over(w)) \
    .withColumn("delta", col("value") - col("prev_value"))
# ← lag("value", 1) = "the value from 1 row ago"
# ← delta = current value - previous value

Sample data flow:

sensor_idtimestampvalue
S11:00 PM100
S11:05 PM105
S11:10 PM98
sensor_idtimestampvalue
S11:00 PM100
S11:05 PM105
S11:10 PM98

Interview Tip: lag(col, N) looks N rows back, lead(col, N) looks N rows forward. You can also provide a default value: lag("value", 1, 0) returns 0 instead of NULL for the first row.

What NOT to Say: "I'd do a self-join on row number." Window functions are the standard approach and far more efficient.

Answer First: For each employee, compare their salary against the average for their department. Keep only those who earn more than the average. Window functions let you compute the department average without a separate groupBy + join.

Memory Map: Find employees whose salary is above the department average -> department partition computes mean salary -> original employee rows retain their values -> comparison filters salaries above the window mean -> grouped checks confirm department boundaries [02_PySpark_Advanced_Operations.md:1753].

Q32: Find employees whose salary is above the department average.

Simple Explanation: For each employee, compare their salary against the average for their department. Keep only those who earn more than the average. Window functions let you compute the department average without a separate groupBy + join.

Technical details:

python β€” editable
from pyspark.sql.functions import avg, col
from pyspark.sql import Window

w = Window.partitionBy("department")
result = df.withColumn("dept_avg", avg("salary").over(w)) \
    .filter(col("salary") > col("dept_avg")) \
    .drop("dept_avg")
# ← avg("salary").over(w) adds a column with the department average to EVERY row
# ← Then we simply filter rows where salary > dept_avg

Sample data flow:

namedepartmentsalary
AliceEng120K
BobEng80K
CharlieEng100K
DianaSales90K
EveSales70K
namedepartmentsalary
AliceEng120K
BobEng80K
CharlieEng100K
DianaSales90K
EveSales70K
namedepartmentsalary
AliceEng120K
DianaSales90K

Interview Tip: This shows the power of window functions over groupBy β€” you keep individual rows while computing group-level metrics. No self-join needed.

What NOT to Say: "I'd groupBy department, compute the average, then join back." That works but is less elegant and less efficient than a single window function.

Answer First: "Find loyal customers who never return products." This is a classic "exists in A but not in B" pattern. The most efficient approach is a left anti join.

Memory Map: Scenario β€” Given two DataFrames (orders and returns), find customers who placed orders but never returned anything -> orders establish candidate customers -> return keys mark disqualifying matches -> left anti join retains unmatched purchasers -> distinct-count reconciliation proves exclusion [02_PySpark_Advanced_Operations.md:1805].

Q33: Scenario β€” Given two DataFrames (orders and returns), find customers who placed orders but never returned anything.

Simple Explanation: "Find loyal customers who never return products." This is a classic "exists in A but not in B" pattern. The most efficient approach is a left_anti join.

Technical details:

python β€” editable
# Method 1: left_anti join (most efficient)
loyal_customers = orders_df.join(returns_df, "customer_id", "left_anti") \
    .select("customer_id").distinct()
# ← left_anti = "from left table, keep rows that DON'T match right table"
# ← Like SQL: WHERE customer_id NOT IN (SELECT customer_id FROM returns)

# Method 2: left_outer + filter (works but less efficient)
loyal_customers = orders_df.join(returns_df, "customer_id", "left_outer") \
    .filter(returns_df["return_id"].isNull()) \
    .select(orders_df["customer_id"]).distinct()
# ← Left join gives NULLs where no match exists, then filter for those NULLs

Sample data flow:

customer_idorder_idcustomer_idreturn_id
C1O1C1R1
C2O2C3R2
C3O3
C4O4
C2← has orders, no returns
C4← has orders, no returns

Interview Tip: Always mention left_anti first β€” it's the most efficient because Spark doesn't need to carry any columns from the right side. It just checks existence. Follow up with the left_outer + filter approach to show you know alternatives.

What NOT to Say: "I'd collect all return customer IDs into a list and use isin()." That collects data to the driver and fails with large datasets.

Answer First: For each product, compare this month's revenue to last month's. Growth rate = (this month - last month) / last month 100%. Use lag() to look back one month.

Memory Map: Calculate month-over-month growth rate per product -> product-month aggregation establishes comparable revenue -> lag retrieves prior month within product -> guarded division computes percentage change -> missing and zero baselines remain explicit [02_PySpark_Advanced_Operations.md:1849].

Q34: Calculate month-over-month growth rate per product.

Simple Explanation: For each product, compare this month's revenue to last month's. Growth rate = (this month - last month) / last month * 100%. Use lag() to look back one month.

Technical details:

python β€” editable
from pyspark.sql.functions import lag, col, round as _round
from pyspark.sql import Window

w = Window.partitionBy("product_id").orderBy("month")

growth = monthly_revenue.withColumn("prev_revenue", lag("revenue", 1).over(w)) \
    .withColumn("mom_growth_pct",
        _round(
            (col("revenue") - col("prev_revenue")) / col("prev_revenue") * 100, 2
        )
    )
# ← lag("revenue", 1) gets last month's revenue
# ← growth = (current - previous) / previous * 100

Sample data flow:

product_idmonthrevenue
P12026-0110000
P12026-0212000
P12026-039000
product_idmonthrevenue
P12026-0110000
P12026-0212000
P12026-039000

Interview Tip: Handle edge cases: what if prev_revenue is 0? Division by zero! Add a when clause: when(col("prev_revenue") > 0, growth_formula).otherwise(None).

What NOT to Say: "I'd self-join the table on month = month - 1." That works but window functions are the standard, cleaner approach.

Answer First: Given order data (which products were in each order), find which product pairs appear together most often. This is the classic "Customers who bought X also bought Y" problem.

Memory Map: Find all pairs of products frequently bought together (market basket analysis) -> basket groups collect distinct products per order -> pair generation creates canonical combinations -> frequency aggregation counts co-occurrence -> support threshold selects meaningful associations [02_PySpark_Advanced_Operations.md:1895].

Q35: Find all pairs of products frequently bought together (market basket analysis).

Simple Explanation: Given order data (which products were in each order), find which product pairs appear together most often. This is the classic "Customers who bought X also bought Y" problem.

Technical details:

python β€” editable
from pyspark.sql.functions import collect_set, explode, col, array_sort
from itertools import combinations

# Step 1: Get all products per order
order_products = df.groupBy("order_id") \
    .agg(collect_set("product_id").alias("products"))
# ← Each row: one order with a list of all products in it

# Step 2: Explode into pairs (using Pandas UDF since built-in is limited)
from pyspark.sql.functions import pandas_udf
from pyspark.sql.types import ArrayType, StructType, StructField, StringType
import pandas as pd

@pandas_udf(ArrayType(StringType()))
def get_pairs(products: pd.Series) -> pd.Series:
    return products.apply(lambda x: [f"{a}|{b}" for a, b in combinations(sorted(x), 2)])
    # ← For each order, generate all unique pairs: {A,B,C} β†’ "A|B", "A|C", "B|C"

# Step 3: Count pair frequencies
pairs = order_products.withColumn("pair", explode(get_pairs(col("products")))) \
    .groupBy("pair").count() \
    .orderBy(col("count").desc())
# ← Most frequent pair = most commonly bought together

Sample data flow:

order_idproduct_id
O1Laptop
O1Mouse
O1Keyboard
O2Laptop
O2Mouse
order_idproducts
O1[Keyboard, Laptop, Mouse]
O2[Laptop, Mouse]
KeyboardLaptop
KeyboardMouse
LaptopMouse
LaptopMouse
paircount
LaptopMouse
KeyboardLaptop
KeyboardMouse

Interview Tip: For very large datasets, consider using Spark's FPGrowth algorithm from MLlib instead of this brute-force approach. Mention it to show ML awareness.

What NOT to Say: "I'd use nested for-loops to compare every pair." That's O(n^2) and not distributed.

Answer First: Real-world data is messy. Some rows might have wrong data types, missing fields, or corrupted characters. Instead of failing the entire pipeline, we separate "good" records from "bad" records and process them differently.

Memory Map: Scenario β€” Process a large CSV with bad records. Keep good records, quarantine bad ones -> permissive parsing separates malformed input -> corrupt-record column retains rejected payload -> valid rows follow typed processing -> quarantine counts reconcile with source totals [02_PySpark_Advanced_Operations.md:1967].

Q36: Scenario β€” Process a large CSV with bad records. Keep good records, quarantine bad ones.

Simple Explanation: Real-world data is messy. Some rows might have wrong data types, missing fields, or corrupted characters. Instead of failing the entire pipeline, we separate "good" records from "bad" records and process them differently.

Analogy: Like airport security β€” valid passengers go through to their gate, flagged items go to a separate inspection area. You don't shut down the whole airport because of one suspicious bag.

Technical details:

python β€” editable
# Use PERMISSIVE mode with corrupt record column
df = spark.read.option("mode", "PERMISSIVE") \
    .option("columnNameOfCorruptRecord", "_corrupt_record") \
    .schema(expected_schema) \
    .csv("/path/to/data.csv")
# ← PERMISSIVE mode: don't fail on bad rows, put them in _corrupt_record column

# Separate good and bad
good_records = df.filter(col("_corrupt_record").isNull()).drop("_corrupt_record")
# ← Rows where _corrupt_record is NULL = successfully parsed = good

bad_records = df.filter(col("_corrupt_record").isNotNull()) \
    .select("_corrupt_record")
# ← Rows where _corrupt_record has a value = failed to parse = bad

# Write both
good_records.write.format("delta").mode("append").saveAsTable("silver_data")
bad_records.write.format("delta").mode("append").saveAsTable("quarantine_data")
# ← Good data continues downstream, bad data goes to quarantine for investigation

The three read modes:

PERMISSIVE (default): Puts corrupt rows in a special column. Pipeline continues.
DROPMALFORMED: Silently drops bad rows. Dangerous β€” you lose data without knowing.
FAILFAST: Throws exception on first bad row. Good for testing, bad for production.

Interview Tip: Always mention the quarantine pattern β€” it shows production maturity. In Databricks, this is part of the Medallion Architecture: bad records go to a quarantine table in the Bronze layer for later investigation.

What NOT to Say: "I'd use FAILFAST mode in production." One bad row would crash your entire pipeline.

Answer First: Spark doesn't have a built-in exact median() function. You have two options: an approximate median (fast, good enough for most cases) or an exact median (uses window functions, more expensive).

Memory Map: a custom aggregation β€” median (not built into Spark SQL) -> group values enter a mergeable buffer -> partial arrays or sketches combine across partitions -> ordered midpoint yields median estimate -> known distributions validate aggregation [02_PySpark_Advanced_Operations.md:2011].

Q37: Implement a custom aggregation β€” median (not built into Spark SQL).

Simple Explanation: Spark doesn't have a built-in exact median() function. You have two options: an approximate median (fast, good enough for most cases) or an exact median (uses window functions, more expensive).

Technical details:

python β€” editable
from pyspark.sql.functions import percentile_approx, expr

# Approximate median (fast, good enough for most cases)
result = df.groupBy("department") \
    .agg(percentile_approx("salary", 0.5).alias("median_salary"))
# ← percentile_approx gives ~99% accurate median with much less computation

# Exact median using window function
from pyspark.sql.functions import count, row_number, col, avg
from pyspark.sql import Window

w = Window.partitionBy("department").orderBy("salary")
total_w = Window.partitionBy("department")

result = df.withColumn("rn", row_number().over(w)) \
    .withColumn("cnt", count("*").over(total_w)) \
    .filter(
        (col("rn") == (col("cnt") / 2).cast("int") + 1) |
        ((col("cnt") % 2 == 0) & (col("rn") == (col("cnt") / 2).cast("int")))
    ) \
    .groupBy("department") \
    .agg(avg("salary").alias("median_salary"))
# ← For odd count: take the middle row
# ← For even count: take the two middle rows and average them

Sample data flow:

salaryrncnt
60K15
70K25
80K35
90K45
100K55
60K14
70K24
80K34
90K44

Interview Tip: Start with percentile_approx β€” it's the pragmatic answer. Mention the exact method as a follow-up. If the interviewer asks about accuracy, percentile_approx with default settings is accurate to within 0.01%.

What NOT to Say: "Spark can't compute median." It can β€” you just need to know the approach.

Answer First: This is a complete end-to-end streaming pipeline. It reads JSON events from Kafka, removes duplicate events (using event id), and writes clean data to a Delta table. This is the bread-and-butter of data engineering on Databricks.

Memory Map: a streaming pipeline that reads from Kafka, deduplicates, and writes to Delta -> Kafka offsets feed streaming input -> event keys and watermark bound deduplication state -> Delta sink commits each batch transactionally -> checkpoint restart proves replay safety [02_PySpark_Advanced_Operations.md:2070].

Q38: Write a streaming pipeline that reads from Kafka, deduplicates, and writes to Delta.

Simple Explanation: This is a complete end-to-end streaming pipeline. It reads JSON events from Kafka, removes duplicate events (using event_id), and writes clean data to a Delta table. This is the bread-and-butter of data engineering on Databricks.

Technical details:

python β€” editable
from pyspark.sql.functions import from_json, col, expr

# Step 1: Define the expected schema
schema = "event_id STRING, user_id STRING, event_type STRING, event_time TIMESTAMP, payload STRING"

# Step 2: Read from Kafka
raw = spark.readStream \
    .format("kafka") \
    .option("kafka.bootstrap.servers", "broker1:9092") \
    .option("subscribe", "events") \
    .option("startingOffsets", "latest") \
    .load()
# ← Kafka gives you key, value, topic, partition, offset, timestamp
# ← The actual event data is in the "value" column as binary

# Step 3: Parse JSON from Kafka's value column
parsed = raw.select(
    from_json(col("value").cast("string"), schema).alias("data")
    # ← cast binary to string, then parse JSON into struct
).select("data.*")
# ← Flatten the struct into individual columns

# Step 4: Deduplicate using watermark
deduped = parsed \
    .withWatermark("event_time", "10 minutes") \
    .dropDuplicates(["event_id"])
# ← Within a 10-minute window, drop events with the same event_id
# ← Watermark bounds the dedup state (only remembers last 10 min of event_ids)

# Step 5: Write to Delta with foreachBatch for MERGE-based dedup
def upsert_events(batch_df, batch_id):
    from delta.tables import DeltaTable
    if DeltaTable.isDeltaTable(spark, "/delta/events"):
        target = DeltaTable.forPath(spark, "/delta/events")
        target.alias("t").merge(
            batch_df.alias("s"), "t.event_id = s.event_id"
        ).whenNotMatchedInsertAll().execute()
        # ← MERGE ensures idempotency: replayed batches don't create duplicates
    else:
        batch_df.write.format("delta").save("/delta/events")
        # ← First batch creates the table

deduped.writeStream \
    .foreachBatch(upsert_events) \
    .option("checkpointLocation", "/checkpoints/events") \
    .trigger(processingTime="30 seconds") \
    .start()
# ← Every 30 seconds: read new Kafka messages β†’ parse β†’ dedup β†’ MERGE into Delta

Interview Tip: This pipeline has TWO levels of deduplication: (1) dropDuplicates removes dupes within the stream, and (2) MERGE prevents dupes against the target table. Mention both β€” it shows thorough thinking.

What NOT to Say: "I'd just use append mode." Without deduplication, duplicate events from Kafka retries will create duplicate rows in your Delta table.

Advanced

Databricks ETL Pipelines

#

Databricks ETL Pipelines

Answer First: Reliable Databricks ETL makes ingestion replayable, transformations deterministic, state bounded, quality observable, and serving contracts explicit.

Memory Map: ingest -> bronze -> validate -> silver -> model -> gold -> monitor.

Delta ownership: This module keeps Databricks integration context concise. Transaction-log, MERGE, time-travel, CDF, and maintenance internals are canonical on /learn/delta.

ETL Scenarios & Pipeline Design

βœ… Pro Tip
Focus: SCD implementations, CDC, Medallion, DLT, end-to-end pipeline design Approach: Every topic starts with simple explanation + analogy β†’ technical depth β†’ code with comments β†’ Interview Tip β†’ What NOT to Say

MEMORY MAP: ETL DESIGN β†’ MEDALS

🧠 M β†’ Medallion Architecture (Bronze β†’ Silver β†’ Gold)
MMedallion Architecture (Bronze β†’ Silver β†’ Gold)
EExtraction (Auto Loader, COPY INTO, readStream)
DDeduplication (ROW_NUMBER, dropDuplicates, MERGE)
AAuto Loader (cloudFiles, schema evolution, exactly-once)
LLate-arriving Data (watermarks, reprocessing, backfill)
SSCD Types (0, 1, 2, 3 β€” know Type 2 cold!)

QUICK VISUAL: MEDALLION = KITCHEN WORKFLOW

🧠 Memory Map
BRONZE (Raw Kitchen Delivery) β†’ Raw ingredients as-is, no cleaning
↓ Schema validation, dedup
SILVER (Prep Station) β†’ Washed, chopped, measured β€” clean & validated
↓ Business logic, aggregation
GOLD (Plated Dish) β†’ Ready to serve to customers (BI dashboards)

SECTION 1: SCD (SLOWLY CHANGING DIMENSION) IMPLEMENTATIONS

Answer First: Slowly changing dimensions define how attribute changes affect warehouse history: Type 1 overwrites, Type 2 versions rows, and Type 3 keeps limited prior values. The reporting history requirement selects the type.

Memory Map: all SCD Types. When would you use each -> all scd types would use each selects the history mutation required for that dimension -> ordered change meets the business key -> current interval closes -> successor version is inserted -> one-current-row assertion verifies history [04_ETL_Scenarios_and_Design.md:35].

Q1: Explain all SCD Types. When would you use each?

Simple Explanation: Dimensions in your data warehouse (like customer name, address, pricing tier) change over time. SCD is a set of strategies for dealing with those changes. The big question: do you want to keep history or not?

Analogies:

  • SCD Type 0 = A printed birth certificate. It never changes. Your date of birth is your date of birth.
  • SCD Type 1 = Overwriting with whiteout. You erase the old value and write the new one. The old value is gone forever.
  • SCD Type 2 = Adding a new page to the history book. The old page stays, a new page is added with the updated info. You can always flip back.
  • SCD Type 3 = A sticky note on the current page. You keep one previous value alongside the current one, but that is all the history you get.

Technical depth:

SCD TypeStrategyHistory?Use Case
Type 0Never updateNoStatic reference data (country codes)
Type 1OverwriteNoCorrections, non-historical attributes
Type 2Add new row + close oldFull historyCustomer address, pricing tiers
Type 3Previous value columnLimited (1 prior)When only the previous value matters
Type 4Separate history tableFull historySeparate current + history tables
Type 6Hybrid (1+2+3)Full + current flag + previousComplex dimensional modeling

Interview Tip: When the interviewer asks about SCD types, always pivot to Type 2. Say "In my experience, Type 2 is the most common production requirement β€” let me walk through the implementation." That shows you have built it, not just read about it.

What NOT to Say: "I would just overwrite the data." That tells the interviewer you have never dealt with audit requirements or historical reporting.

Answer First: When to use it: Correcting typos, updating non-business-critical fields, or when compliance explicitly says "do NOT keep old PII."

Memory Map: SCD Type 1 (Overwrite) in Databricks -> matched business key overwrites current attributes -> no history row is created -> target retains only the corrected state -> merge counts verify the in-place update [04_ETL_Scenarios_and_Design.md:63].

Q2: Implement SCD Type 1 (Overwrite) in Databricks.

Simple Explanation: SCD Type 1 = whiteout. Customer changed their email? Just overwrite the old email. No history kept. This is the simplest approach β€” a basic MERGE/upsert.

When to use it: Correcting typos, updating non-business-critical fields, or when compliance explicitly says "do NOT keep old PII."

Code:

sql
MERGE INTO dim_customer t                          -- # ← target: our existing dimension table
USING staging_customer s                           -- # ← source: incoming changes
ON t.customer_id = s.customer_id                   -- # ← match on business key

WHEN MATCHED THEN                                  -- # ← record exists β†’ overwrite it
    UPDATE SET
        t.name = s.name,                           -- # ← old name is GONE (whiteout)
        t.email = s.email,                         -- # ← old email is GONE
        t.address = s.address,
        t.phone = s.phone,
        t.updated_at = current_timestamp()         -- # ← track when we overwrote

WHEN NOT MATCHED THEN                              -- # ← brand new customer β†’ insert
    INSERT (customer_id, name, email, address, phone, created_at, updated_at)
    VALUES (s.customer_id, s.name, s.email, s.address, s.phone,
            current_timestamp(), current_timestamp())

Data walkthrough β€” BEFORE merge:

customer_idnameemailaddress
C001Alicealice@old.com123 Main St

Source data arriving:

customer_idnameemailaddress
C001Alicealice@new.com456 Oak Ave

AFTER merge (Type 1 β€” overwrite):

customer_idnameemailaddress
C001Alicealice@new.com456 Oak Ave

The old email and address are gone forever. No history.

Interview Tip: Always mention that Type 1 is NOT suitable when you need audit trails. Show you understand the tradeoff.

What NOT to Say: "Type 1 is the best approach for dimensions." It depends entirely on the business requirement.

Answer First: SCD Type 2 = adding a new page to the history book. When a customer moves to a new address, you do NOT erase the old address. Instead, you close the old record (mark it as no longer current) and insert a brand new row with the new address.

Memory Map: SCD Type 2 (Full History) β€” This is the #1 most asked question -> business key locates the active dimension row -> changed attributes close its validity interval -> a successor row receives the new values -> uniqueness and date assertions protect history [04_ETL_Scenarios_and_Design.md:116].

Q3: Implement SCD Type 2 (Full History) β€” This is the #1 most asked question.

Simple Explanation: SCD Type 2 = adding a new page to the history book. When a customer moves to a new address, you do NOT erase the old address. Instead, you close the old record (mark it as no longer current) and insert a brand new row with the new address. Now you have full history: you can answer "Where did this customer live in March 2024?"

Real-world analogy: Think of a passport. When you renew it, your old passport gets stamped "CANCELLED" with an end date, and you get a new passport with a new start date. Both exist. You can always show your travel history from the old one.

Table structure:

sql
CREATE TABLE dim_customer (
    surrogate_key BIGINT GENERATED ALWAYS AS IDENTITY,  -- # ← auto-incrementing PK
    customer_id STRING,          -- # ← Business key (stays same across versions)
    name STRING,
    email STRING,
    address STRING,
    hash_value STRING,           -- # ← Hash of tracked columns for change detection
    effective_start TIMESTAMP,   -- # ← When this version became active
    effective_end TIMESTAMP,     -- # ← When this version was retired (9999-12-31 = still active)
    is_current BOOLEAN           -- # ← Quick filter for "give me current state"
) USING DELTA;

Data walkthrough β€” BEFORE merge (one current record):

surrogate_keycustomer_idnameaddresseffective_starteffective_endis_current
1C001Alice123 Main St2024-01-019999-12-31true

Source data arriving (Alice moved):

customer_idnameaddress
C001Alice456 Oak Ave

AFTER merge (Type 2 β€” old row closed, new row inserted):

surrogate_keycustomer_idnameaddresseffective_starteffective_endis_current
1C001Alice123 Main St2024-01-012025-03-15 10:00:00false
2C001Alice456 Oak Ave2025-03-15 10:00:009999-12-31true

Both rows exist. The old page stays in the history book. The new page is now current.

Full PySpark implementation:

python β€” editable
from delta.tables import DeltaTable
from pyspark.sql.functions import *

# Step 1: Prepare source with hash for change detection
# ← Hash lets us quickly detect if anything actually changed
source_df = spark.table("staging_customer").withColumn(
    "hash_value", md5(concat_ws("||", col("name"), col("email"), col("address")))
)

# Step 2: Get current target records
target = DeltaTable.forName(spark, "dim_customer")
target_df = target.toDF().filter("is_current = true")  # ← Only compare against active rows

# Step 3: Identify changes (new + changed records)
changes = source_df.alias("s").join(
    target_df.alias("t"),
    col("s.customer_id") == col("t.customer_id"),
    "left"
).filter(
    col("t.customer_id").isNull() |           # ← New records (no match in target)
    (col("s.hash_value") != col("t.hash_value"))  # ← Changed records (hash differs)
).select("s.*")

# Step 4: THE MERGE KEY TRICK (this is the clever part!)
# Problem: For a changed record, we need to BOTH:
#   1. UPDATE the old row (set is_current=false)  β†’ needs to MATCH
#   2. INSERT the new row (new current version)    β†’ needs to NOT MATCH
# But MERGE only does one action per matched row!
#
# Solution: Create TWO rows per change:
#   Row 1: merge_key = customer_id  β†’ MATCHES target β†’ triggers UPDATE (close old)
#   Row 2: merge_key = NULL         β†’ NULL never matches β†’ triggers INSERT (new row)

# Rows that need to CLOSE existing records (update is_current = false)
close_records = changes.filter(
    col("customer_id").isin(
        [row.customer_id for row in target_df.join(changes, "customer_id", "inner")
         .select("customer_id").distinct().collect()]
    )
).withColumn("merge_key", col("customer_id"))  # ← Real key β†’ will MATCH

# Rows that need to INSERT as new current records
insert_records = changes.withColumn("merge_key", lit(None).cast("string"))  # ← NULL β†’ will NOT MATCH

# Combine: close_records will MATCH (update), insert_records will NOT MATCH (insert)
staged = close_records.unionByName(insert_records)

# Step 5: Execute MERGE
target.alias("t").merge(
    staged.alias("s"),
    "t.customer_id = s.merge_key AND t.is_current = true"  # ← Only match current rows
).whenMatchedUpdate(set={
    "is_current": lit(False),                              # ← Close the old record
    "effective_end": current_timestamp()                   # ← Stamp the retirement date
}).whenNotMatchedInsert(values={
    "customer_id": col("s.customer_id"),
    "name": col("s.name"),
    "email": col("s.email"),
    "address": col("s.address"),
    "hash_value": col("s.hash_value"),
    "effective_start": current_timestamp(),                # ← New version starts now
    "effective_end": lit("9999-12-31 23:59:59").cast("timestamp"),  # ← Open-ended
    "is_current": lit(True)                                # ← This is now the current version
}).execute()

Interview Tip: The merge-key trick is the most impressive thing you can explain in a Databricks interview. Draw it on the whiteboard: "I create two rows per change β€” one with a real key to close the old record, one with NULL to insert the new record β€” all in a single atomic MERGE."

What NOT to Say: "I would do two separate operations β€” first update, then insert." That is not atomic and can leave your table in an inconsistent state if the job fails between the two steps.

Answer First: The merge-key trick emits one source row that matches and closes the current dimension version and a second row with a null merge key that inserts its successor. This performs both SCD Type 2 transitions atomically.

Memory Map: the "Merge Key Trick" for SCD Type 2. Why is it needed -> one staged copy carries the real business key -> that copy matches and expires the active row -> a second copy carries a null merge key -> unmatched processing inserts the successor [04_ETL_Scenarios_and_Design.md:233].

Q4: Explain the "Merge Key Trick" for SCD Type 2. Why is it needed?

Simple Explanation: The trick solves a fundamental problem: for a changed record, you need to BOTH:

  1. UPDATE the existing row (set is_current=false, effective_end=now)
  2. INSERT a new row (the new current version)

But MERGE can only do one action per matched source row.

Analogy: Imagine a librarian processing a returned book. They need to (1) stamp the return date on the old checkout card AND (2) create a new checkout card for the next borrower. But the library system only lets you do one action per scan. The trick: scan the book twice β€” once with the real barcode (to close the old card) and once with a fake barcode that does not match anything (to create the new card).

The trick:

  • Create TWO rows in the staged source for each change:
    • Row 1: merge_key = customer_id β†’ this will MATCH the target β†’ UPDATE (close old record)
    • Row 2: merge_key = NULL β†’ NULL never matches β†’ goes to NOT MATCH β†’ INSERT (new current record)
  • Union both and execute a single MERGE

Visual walkthrough:

πŸ“ Architecture Diagram
Source change: customer C001 moved from "123 Main" to "456 Oak"

Staged DataFrame (after the trick):
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ merge_key    β”‚ customer_id  β”‚ address     β”‚ Purpose      β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ C001         β”‚ C001         β”‚ 456 Oak Ave β”‚ β†’ MATCHES    β”‚ β†’ UPDATE old row (close it)
β”‚ NULL         β”‚ C001         β”‚ 456 Oak Ave β”‚ β†’ NO MATCH   β”‚ β†’ INSERT new row (open it)
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Single MERGE handles both in one atomic operation!

Alternative approach β€” Two-step MERGE:

python β€” editable
# Step 1: Close old records
target.alias("t").merge(
    changes.alias("s"),
    "t.customer_id = s.customer_id AND t.is_current = true"
).whenMatchedUpdate(set={
    "is_current": lit(False),
    "effective_end": current_timestamp()
}).execute()

# Step 2: Insert new current records
changes.withColumn("effective_start", current_timestamp()) \
    .withColumn("effective_end", lit("9999-12-31").cast("timestamp")) \
    .withColumn("is_current", lit(True)) \
    .write.format("delta").mode("append").saveAsTable("dim_customer")

Interview Tip: Know both approaches. The merge-key trick is elegant (one atomic operation). The two-step approach is simpler to read but not atomic. Mention both and explain why you prefer the single-MERGE approach in production.

What NOT to Say: "I just do two separate writes." Without explaining atomicity concerns, this sounds like you have never dealt with pipeline failures mid-execution.

Answer First: DLT makes SCD Type 2 trivially easy. Instead of writing 50+ lines of merge-key tricks, you write 5 lines and DLT handles everything: closing old records, inserting new ones, managing timestamps, and even handling out-of-order events.

Memory Map: SCD Type 2 using Delta Live Tables (DLT) β€” the modern approach -> sequence column orders competing changes -> declared keys identify the tracked entity -> managed apply-changes closes and inserts versions -> expectation checks protect incoming records [04_ETL_Scenarios_and_Design.md:289].

Q5: Implement SCD Type 2 using Delta Live Tables (DLT) β€” the modern approach.

Simple Explanation: DLT makes SCD Type 2 trivially easy. Instead of writing 50+ lines of merge-key tricks, you write 5 lines and DLT handles everything: closing old records, inserting new ones, managing timestamps, and even handling out-of-order events.

Analogy: The manual approach (Q3) is like hand-washing every dish. DLT is a dishwasher β€” you load the dishes, press a button, and it handles the rest.

Code:

python β€” editable
import dlt
from pyspark.sql.functions import col, expr

# Bronze: raw CDC events
@dlt.table(comment="Raw customer CDC events")
def bronze_customer_cdc():
    return (
        spark.readStream.format("cloudFiles")      # ← Auto Loader ingests new files
        .option("cloudFiles.format", "json")        # ← Source format
        .option("cloudFiles.schemaLocation", "/schema/customer_cdc")  # ← Schema tracking
        .load("/landing/customer_cdc/")             # ← Watch this folder
    )

# Silver: SCD Type 2 using apply_changes β€” THIS IS THE MAGIC
dlt.create_streaming_table("dim_customer_scd2")     # ← Create the target table

dlt.apply_changes(
    target="dim_customer_scd2",                     # ← Where to write
    source="bronze_customer_cdc",                   # ← Where to read from
    keys=["customer_id"],                           # ← Business key for matching
    sequence_by=col("updated_at"),                  # ← How to order events (handles late arrivals!)
    apply_as_deletes=expr("operation = 'DELETE'"),  # ← Which events are deletes
    except_column_list=["operation", "_rescued_data"],  # ← Columns to exclude from target
    stored_as_scd_type=2                            # ← THE ONE LINE THAT DOES IT ALL
)
# DLT automatically manages __START_AT, __END_AT, __IS_CURRENT columns

Why this is superior:

  • No manual merge-key trick
  • Handles out-of-order events via sequence_by
  • Handles deletes natively
  • Automatic state management
  • Built-in data quality with expectations

Interview Tip: Always mention DLT as the "modern" approach, then show you also know the manual approach. This demonstrates both practical skills and deep understanding.

What NOT to Say: "I only know the DLT way." Interviewers want to know you understand what DLT is doing under the hood. Show both manual and DLT approaches.

Answer First: SCD Type 3 keeps exactly one previous value. Instead of a full history (Type 2), you add a "previous address" column. When the address changes, the current address moves to "previous address" and the new address takes its place.

Memory Map: SCD Type 3 (Previous Value Column) -> changed attribute copies current data into a previous column -> new data replaces the current column -> only one prior value remains -> row comparison verifies limited history [04_ETL_Scenarios_and_Design.md:339].

Q6: Implement SCD Type 3 (Previous Value Column).

Simple Explanation: SCD Type 3 keeps exactly one previous value. Instead of a full history (Type 2), you add a "previous_address" column. When the address changes, the current address moves to "previous_address" and the new address takes its place.

Analogy: Like a phone that shows your current wallpaper and your "previous wallpaper" in settings β€” you can go back one step, but that is it. No full history.

Code:

sql
MERGE INTO dim_customer t
USING staging_customer s
ON t.customer_id = s.customer_id

WHEN MATCHED AND t.address != s.address THEN       -- # ← Address actually changed
    UPDATE SET
        t.prev_address = t.address,                 -- # ← Save current as previous (the "sticky note")
        t.address = s.address,                      -- # ← Update with new value
        t.address_changed_date = current_timestamp(),
        t.updated_at = current_timestamp()

WHEN MATCHED AND t.address = s.address THEN         -- # ← Address same, but other fields changed
    UPDATE SET                                       -- # ← Other fields might change
        t.name = s.name,
        t.email = s.email,
        t.updated_at = current_timestamp()

WHEN NOT MATCHED THEN                               -- # ← Brand new customer
    INSERT (customer_id, name, email, address, prev_address, created_at, updated_at)
    VALUES (s.customer_id, s.name, s.email, s.address, NULL,
            current_timestamp(), current_timestamp())

Data walkthrough:

customer_idaddressprev_address(before merge)
C001123 Main StNULL

Source: C001 address changed to "456 Oak Ave"

customer_idaddressprev_address(after merge)
C001456 Oak Ave123 Main St

Source: C001 address changed AGAIN to "789 Pine Rd"

customer_idaddressprev_address(after second merge)
C001789 Pine Rd456 Oak Ave"123 Main St" is LOST!

Interview Tip: Mention that Type 3 only keeps ONE previous value. If the interviewer asks "What if we need two previous values?" β€” that is when you pivot to Type 2 or Type 6 (hybrid).

What NOT to Say: "Type 3 keeps full history." It does not. It only keeps one step back.

Answer First: Large Type 2 dimensions remain manageable when change detection is incremental, source keys are deduplicated, and MERGE scans are pruned to affected records. File layout and match selectivity matter more than table size alone.

Memory Map: Scenario β€” Implement SCD Type 2 for a customer dimension with 500M records, receiving 2M daily updates. Design the solution at scale -> incremental change detection limits daily input -> deterministic ordering removes duplicate keys -> match predicates prune the large target -> file and operation metrics validate scale [04_ETL_Scenarios_and_Design.md:395].

Q7: Scenario β€” Implement SCD Type 2 for a customer dimension with 500M records, receiving 2M daily updates. Design the solution at scale.

Simple Explanation: This is a scale question. The interviewer wants to know: can you handle SCD Type 2 when the dimension table is huge? The answer is yes, but you need to be smart about partitioning, change detection, deduplication, and MERGE optimization.

Analogy: Updating a phone book with 500 million entries. You received 2 million address change cards today. You do not flip through all 500 million pages β€” you sort the change cards, find only the relevant pages, and update just those.

Architecture:

πŸ“ Architecture Diagram
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Source DB    │────▢│ CDC Pipeline  │────▢│ dim_customer     β”‚
β”‚ (500M rows) β”‚     β”‚ (2M changes)  β”‚     β”‚ (500M+ rows)     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Key Design Decisions:

1. PARTITIONING: Partition dim_customer by customer_id hash range
   - 500M rows / 1 GB per partition β‰ˆ 500 partitions
   - Ensures MERGE only scans relevant partitions

2. Z-ORDERING: ZORDER BY (customer_id) for data skipping during MERGE

3. CHANGE DETECTION: Use hash comparison
   - Hash only tracked columns (name, email, address)
   - Reduces comparison cost

4. SOURCE DEDUPLICATION:
   - 2M daily changes may have duplicates
   - Deduplicate BEFORE merge (keep latest per customer_id)

5. MERGE OPTIMIZATION:
   - Add partition column to MERGE condition
   - Broadcast the 2M source (it's small relative to 500M)
   - Use Photon runtime

6. COMPACTION: Run OPTIMIZE weekly
   - Z-ORDER BY (customer_id, is_current)

7. MONITORING:
   - Track merge duration, rows affected
   - Alert if > 10M changes (unusual spike)
   - Data quality: null checks, hash validation
# Optimized implementation
from delta.tables import DeltaTable
from pyspark.sql.functions import *
# Deduplicate source (keep latest per customer)
# ← 2M changes may have multiple updates for same customer in one day
w = Window.partitionBy("customer_id").orderBy(col("updated_at").desc())
source = spark.table("staging_customer") \
.withColumn("rn", row_number().over(w)) \
.filter(col("rn") == 1).drop("rn") \ # ← Keep only the latest change
.withColumn("hash_value", md5(concat_ws("||", col("name"), col("email"), col("address"))))
# Get only changed records (compare hashes)
# ← Don't merge all 2M β€” only merge the ones that actually changed
target_current = spark.table("dim_customer").filter("is_current = true") \
.select("customer_id", col("hash_value").alias("target_hash"))
changes = source.join(target_current, "customer_id", "left") \
.filter(col("target_hash").isNull() | (col("hash_value") != col("target_hash"))) \
.drop("target_hash") # ← Only truly changed records remain
# Execute SCD Type 2 merge (using the merge-key trick from Q3)
# ... (same pattern as Q3)

Interview Tip: This is an architecture question. Talk through each decision: "First I would deduplicate, then hash-compare to filter unchanged records, then use the merge-key trick, and finally OPTIMIZE weekly." Show you think about the full lifecycle.

What NOT to Say: "I would just run a MERGE on all 500M records every day." That shows no understanding of scale optimization.

SECTION 2: CHANGE DATA CAPTURE (CDC)

Answer First: CDC (Change Data Capture) means capturing every INSERT, UPDATE, and DELETE from a source database and replaying those changes into your Delta Lake. Instead of doing a full table dump every day, you capture only what changed β€” much more efficient.

Memory Map: a CDC pipeline from Oracle/MySQL to Delta Lake -> cdc pipeline from oracle mysql to delta lake anchors the source mutation and replay order -> source log position orders mutations -> operation envelope preserves inserts, updates, and deletes -> consumer applies changes idempotently -> offset reconciliation proves completeness [04_ETL_Scenarios_and_Design.md:473].

Q8: Design a CDC pipeline from Oracle/MySQL to Delta Lake.

Simple Explanation: CDC (Change Data Capture) means capturing every INSERT, UPDATE, and DELETE from a source database and replaying those changes into your Delta Lake. Instead of doing a full table dump every day, you capture only what changed β€” much more efficient.

Analogy: Instead of photocopying every page of a book every day, you just photocopy the pages that were edited today. Much less work, much faster.

Architecture:

πŸ“ Architecture Diagram
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Oracle   │────▢│ Debezium │────▢│ Kafka │────▢│ Auto Loader  │────▢│ Bronze       β”‚
β”‚ (source) β”‚     β”‚ (CDC)    β”‚     β”‚       β”‚     β”‚ (ingestion)  β”‚     β”‚ (raw events) β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜
                                                                            β”‚
                                                                    β”Œβ”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”
                                                                    β”‚ Silver        β”‚
                                                                    β”‚ (MERGE/SCD)   β”‚
                                                                    β””β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜
                                                                            β”‚
                                                                    β”Œβ”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”
                                                                    β”‚ Gold          β”‚
                                                                    β”‚ (aggregated)  β”‚
                                                                    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Code:

# Bronze: Ingest raw CDC events from Kafka
raw_cdc = spark.readStream.format("kafka") \
.option("kafka.bootstrap.servers", "broker:9092") \ # ← Kafka cluster address
.option("subscribe", "oracle.schema.customers") \ # ← Debezium topic naming convention
.load() \
.select(
from_json(col("value").cast("string"), cdc_schema).alias("data"), # ← Parse JSON payload
col("timestamp").alias("kafka_timestamp") # ← When Kafka received it
).select("data.*", "kafka_timestamp")
raw_cdc.writeStream.format("delta") \
.outputMode("append") \ # ← Always append raw events
.option("checkpointLocation", "/checkpoints/bronze_cdc") \ # ← Exactly-once guarantee
.table("bronze_customer_cdc")
# Silver: Apply CDC using foreachBatch
def apply_cdc_changes(batch_df, batch_id):
from delta.tables import DeltaTable
# Deduplicate within batch (keep latest per key)
# ← Same customer may have multiple changes in one batch
w = Window.partitionBy("after.id").orderBy(col("ts_ms").desc())
deduped = batch_df.withColumn("rn", row_number().over(w)) \
.filter("rn = 1").drop("rn")
target = DeltaTable.forName(spark, "silver_customers")
target.alias("t").merge(
deduped.alias("s"),
"t.customer_id = s.after.id" # ← Match on business key
).whenMatchedDelete(
condition="s.op = 'd'" # ← Delete operation
).whenMatchedUpdate(
condition="s.op = 'u'", # ← Update operation
set={
"name": "s.after.name",
"email": "s.after.email",
"address": "s.after.address",
"updated_at": "s.ts_ms"
}
).whenNotMatchedInsert(
condition="s.op IN ('c', 'r')", # ← Create / Read (snapshot)
values={
"customer_id": "s.after.id",
"name": "s.after.name",
"email": "s.after.email",
"address": "s.after.address",
"created_at": "s.ts_ms",
"updated_at": "s.ts_ms"
}
).execute()
spark.readStream.table("bronze_customer_cdc") \
.writeStream.foreachBatch(apply_cdc_changes) \ # ← Process micro-batches
.option("checkpointLocation", "/checkpoints/silver_customers") \
.trigger(processingTime="1 minute") \ # ← Check every minute
.start()

Interview Tip: Mention Debezium by name. Say "I would use Debezium for log-based CDC because it reads the database's transaction log directly β€” no triggers, no polling, minimal impact on the source."

What NOT to Say: "I would poll the source table every 5 minutes with a SELECT." That is timestamp-based CDC β€” it misses deletes, has higher source load, and is not real CDC.

Delta integration summary β€” Q9: What is Delta Lake Change Data Feed (CDF)? How is it different from CDC?: Databricks pipelines can consume Delta CDF for incremental downstream work; feature enablement, change metadata, retention, and read APIs remain Delta-owned details. Open the canonical Delta answer.

Answer First: Debezium reads the source database's transaction log and produces events. Each event has an op field telling you what happened.

Memory Map: Debezium CDC event types β€” what do they mean -> connector reads ordered database log records -> operation code distinguishes create update delete and snapshot -> before and after images preserve row state -> source position enables replay and reconciliation [04_ETL_Scenarios_and_Design.md:620].

Q10: Debezium CDC event types β€” what do they mean?

Simple Explanation: Debezium reads the source database's transaction log and produces events. Each event has an op field telling you what happened.

Operation (op)MeaningWhen
rRead (snapshot)Initial full load
cCreateNew row inserted
uUpdateRow updated
dDeleteRow deleted

Each event has:

  • before: Row state before the change (null for inserts)
  • after: Row state after the change (null for deletes)
  • ts_ms: Timestamp of the change
  • source: Source metadata (database, table, position)

Interview Tip: If asked "How do you handle the initial load with Debezium?", mention the snapshot mode: Debezium first does a full table scan (producing r events), then switches to reading the transaction log (producing c, u, d events).

What NOT to Say: "Debezium only captures inserts and updates." It captures deletes too β€” that is the whole point of log-based CDC.

SECTION 3: MEDALLION ARCHITECTURE

Answer First: The medallion architecture is a three-layer data organization pattern: Bronze (raw), Silver (clean), Gold (business-ready). Each layer adds more quality and structure.

Memory Map: a medallion architecture. What are the design decisions for each layer -> medallion architecture design decisions for each layer assigns distinct responsibilities across medallion layers -> raw events land with replay metadata -> Silver enforces keys and quality -> Gold publishes a business contract -> reconciliation gates each promotion [04_ETL_Scenarios_and_Design.md:646].

Q11: Design a medallion architecture. What are the design decisions for each layer?

Simple Explanation: The medallion architecture is a three-layer data organization pattern: Bronze (raw), Silver (clean), Gold (business-ready). Each layer adds more quality and structure.

Analogy β€” The Kitchen Workflow:

πŸ“ Architecture Diagram
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                        THE KITCHEN ANALOGY                              β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                                                         β”‚
β”‚  BRONZE = Raw Kitchen Delivery                                          β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                                                       β”‚
β”‚  β”‚ πŸ“¦ Boxes of  β”‚  Raw ingredients arrive. Don't wash, don't sort.      β”‚
β”‚  β”‚ ingredients  β”‚  Just stack them in the walk-in fridge exactly as-is. β”‚
β”‚  β”‚ as delivered β”‚  Keep the receipt (metadata). Never throw anything    β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  away β€” you might need to recheck the delivery.       β”‚
β”‚        ↓                                                                β”‚
β”‚  SILVER = Prep Station                                                  β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                                                       β”‚
β”‚  β”‚ πŸ”ͺ Washed,   β”‚  Wash the vegetables, trim the meat, measure the     β”‚
β”‚  β”‚ chopped,     β”‚  spices. Throw out anything rotten (bad records).     β”‚
β”‚  β”‚ measured     β”‚  Everything is clean, validated, and ready to cook.   β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                                                       β”‚
β”‚        ↓                                                                β”‚
β”‚  GOLD = Plated Dish                                                     β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                                                       β”‚
β”‚  β”‚ 🍽️ Ready to  β”‚  The finished dish, beautifully plated. This is what β”‚
β”‚  β”‚ serve        β”‚  goes to the customer (BI dashboards, reports).       β”‚
β”‚  β”‚              β”‚  Pre-aggregated, denormalized, fast to query.         β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                                                       β”‚
β”‚                                                                         β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

BRONZE (Raw Ingestion):

# Design decisions:
# - Append-only (NEVER update or delete raw data) ← # Like keeping every delivery receipt
# - Include ingestion metadata ← # When did it arrive? From where?
# - Partition by ingestion date (not business date) ← # Organize by when WE received it
# - Schema: flexible (can store as strings or use schema inference)
# - Retention: long (compliance, reprocessing)
bronze_df = spark.readStream.format("cloudFiles") \
.option("cloudFiles.format", "json") \ # ← Accept raw JSON as-is
.option("cloudFiles.schemaLocation", schema_loc) \ # ← Track schema evolution
.load(landing_path) \
.withColumn("_ingested_at", current_timestamp()) \ # ← When did we receive this?
.withColumn("_source_file", input_file_name()) \ # ← Which file did it come from?
.withColumn("_batch_id", lit(batch_id)) # ← Which batch run?
bronze_df.writeStream.format("delta") \
.partitionBy("_ingested_date") \ # ← Partition by ingestion date
.option("checkpointLocation", checkpoint) \
.table("bronze_orders")

SILVER (Cleansed, Conformed):

# Design decisions:
# - Apply data quality checks ← # Throw out rotten ingredients
# - Deduplicate ← # No double-counting
# - Standardize data types and values ← # Everything in the same units
# - Join reference data ← # Enrich with dim tables
# - Apply SCD logic here ← # Dimension management
# - Enable CDF for downstream ← # So Gold knows what changed
# - Partition by business date ← # Now organize by business meaning
silver_df = spark.readStream.table("bronze_orders") \
.dropDuplicates(["order_id"]) \ # ← Remove duplicate orders
.filter(col("order_id").isNotNull()) \ # ← Reject nulls
.filter(col("amount") > 0) \ # ← Reject invalid amounts
.withColumn("amount", col("amount").cast("decimal(10,2)")) \ # ← Standardize type
.withColumn("order_date", to_date(col("order_date_str"), "yyyy-MM-dd")) # ← Parse date

GOLD (Business-Level Aggregates):

python β€” editable
# Design decisions:
# - Pre-aggregated for specific use cases               ← # The plated dish
# - Denormalized (star schema or wide tables)           ← # No joins needed at query time
# - Heavily optimized (Z-ORDER, OPTIMIZE, caching)     ← # Fast to serve
# - Lower volume, higher query performance              ← # Small but mighty

gold_df = spark.table("silver_orders") \
    .groupBy("order_date", "category", "region") \
    .agg(
        count("*").alias("order_count"),                 # ← How many orders?
        sum("amount").alias("total_revenue"),            # ← How much money?
        avg("amount").alias("avg_order_value"),          # ← Average ticket size?
        countDistinct("customer_id").alias("unique_customers")  # ← How many unique buyers?
    )

Interview Tip: Use the kitchen analogy. It shows you can explain architecture to non-technical stakeholders, which is a senior/lead skill.

What NOT to Say: "Bronze is just a staging area we can delete." Never. Bronze is your source of truth for reprocessing. If Silver has a bug, you reprocess from Bronze. Deleting Bronze means you have to re-extract from source.

Answer First: Medallion is a guideline, not a religion. Sometimes you skip layers or add layers. The key is understanding WHY you deviate.

Memory Map: When would you deviate from the standard medallion pattern -> latency and governance requirements may justify fewer layers -> direct promotion removes redundant storage -> explicit contracts replace the skipped boundary -> reconciliation still guards published data [04_ETL_Scenarios_and_Design.md:747].

Q12: When would you deviate from the standard medallion pattern?

Simple Explanation: Medallion is a guideline, not a religion. Sometimes you skip layers or add layers. The key is understanding WHY you deviate.

  1. Skip Silver: Simple use cases with minimal transformation (raw β†’ gold)
  2. Real-time "hot path": Streaming direct to serving layer, bypassing medallion
  3. ML Feature Layer: Separate "feature store" alongside gold
  4. Reverse ETL: Gold feeds back to operational systems (CRM, marketing tools)
  5. "Platinum" layer: Some orgs add a presentation/BI-specific layer
  6. Domain-specific layers: In data mesh, each domain owns its own medallion stack

Interview Tip: Mention one time you deviated and why. For example: "We added a 'feature store' layer parallel to Gold because ML models needed different aggregation windows than BI dashboards."

What NOT to Say: "We always follow medallion exactly as defined." That sounds like you follow templates without thinking. Senior engineers adapt patterns to fit their use case.

Answer First: Bad data flows downstream and corrupts everything. The pattern: quarantine bad records at each layer boundary. Good records move forward, bad records go to a separate quarantine table for investigation.

Memory Map: you handle data quality between medallion layers -> expectations evaluate records before promotion -> invalid rows fail drop or enter quarantine by severity -> quality metrics publish violation counts -> downstream layers consume only approved state [04_ETL_Scenarios_and_Design.md:765].

Q13: How do you handle data quality between medallion layers?

Simple Explanation: Bad data flows downstream and corrupts everything. The pattern: quarantine bad records at each layer boundary. Good records move forward, bad records go to a separate quarantine table for investigation.

Analogy: Airport security checkpoints. Passengers (records) go through screening between gates (layers). If they pass, they proceed. If not, they are pulled aside for inspection (quarantine table).

Code:

python β€” editable
# Pattern: Quarantine bad records, pass good records

from pyspark.sql.functions import col, when

bronze_df = spark.read.table("bronze_orders")

# Define quality rules
quality_rules = {
    "valid_order_id": col("order_id").isNotNull(),                               # ← Must have an ID
    "positive_amount": col("amount") > 0,                                         # ← No negative orders
    "valid_date": col("order_date").isNotNull() & (col("order_date") >= "2020-01-01"),  # ← Reasonable date
    "valid_status": col("status").isin("pending", "completed", "cancelled", "shipped"),  # ← Known statuses only
}

# Apply all rules
quality_check = bronze_df
for rule_name, condition in quality_rules.items():
    quality_check = quality_check.withColumn(
        f"_rule_{rule_name}", when(condition, True).otherwise(False)              # ← Tag each rule result
    )

# All rules must pass
all_rules = [f"_rule_{name}" for name in quality_rules]
quality_check = quality_check.withColumn(
    "_is_valid",
    reduce(lambda a, b: a & b, [col(r) for r in all_rules])                      # ← AND all rules together
)

# Route records
good_records = quality_check.filter("_is_valid = true").drop(*all_rules, "_is_valid")
bad_records = quality_check.filter("_is_valid = false")                           # ← Keep rule columns for debugging

good_records.write.format("delta").mode("append").saveAsTable("silver_orders")    # ← Move forward
bad_records.write.format("delta").mode("append").saveAsTable("quarantine_orders") # ← Investigate later

Interview Tip: Always mention the quarantine pattern. Say "We never silently drop bad records β€” we quarantine them so the data team can investigate root causes and fix upstream issues."

What NOT to Say: "We just filter out bad records." Without quarantine, you have no visibility into data quality issues. You are hiding problems, not solving them.

SECTION 4: AUTO LOADER

Answer First: Auto Loader watches a cloud storage folder and automatically picks up new files as they arrive. It tracks which files it has already processed (via checkpoints), so it never processes the same file twice.

Memory Map: Auto Loader? How does it work -> auto loader it work selects file discovery and schema-state behavior -> cloud discovery identifies unseen objects -> schema and file state persist in checkpoint storage -> incremental batch ingests each object -> rescued-data metrics expose drift [04_ETL_Scenarios_and_Design.md:818].

Q14: What is Auto Loader? How does it work?

Simple Explanation: Auto Loader watches a cloud storage folder and automatically picks up new files as they arrive. It tracks which files it has already processed (via checkpoints), so it never processes the same file twice.

Analogy: Auto Loader is like a security camera watching a folder. The moment a new file lands, it detects it and processes it. It keeps a log of every file it has seen, so even if you restart the camera, it does not reprocess old footage.

Two modes:

AspectDirectory ListingFile Notification
HowLists directory for new filesCloud events (S3 SNS/SQS, ADLS Event Grid)
SetupZero configurationRequires cloud infrastructure
LatencyHigher (polling interval)Near real-time
CostCan be expensive for large directoriesMinimal listing costs
ScaleSlower for millions of filesHandles billions of files
RecommendationDev/test, small directoriesProduction, high-volume

Fully commented Auto Loader config:

df = spark.readStream.format("cloudFiles") \ # ← "cloudFiles" = Auto Loader
.option("cloudFiles.format", "json") \ # ← Source file format (json, csv, parquet, avro)
.option("cloudFiles.schemaLocation", "/schema/orders") \ # ← Where to store inferred/evolved schema
.option("cloudFiles.inferColumnTypes", "true") \ # ← Infer types (not just strings)
.option("cloudFiles.useNotifications", "true") \ # ← File notification mode (event-driven)
.option("cloudFiles.schemaEvolutionMode", "addNewColumns") \ # ← Auto-add new columns
.option("cloudFiles.maxFilesPerTrigger", "1000") \ # ← Limit files per micro-batch (backpressure)
.option("cloudFiles.schemaHints", "order_id LONG, amount DECIMAL(10,2)") \ # ← Force specific types
.load("/landing/orders/") # ← The folder to watch

Interview Tip: Mention both modes and explain when to use each. Say "In production, I use file notification mode because it scales to billions of files without expensive directory listings."

What NOT to Say: "Auto Loader is just a file reader." It is much more β€” it provides exactly-once processing, schema evolution, and file tracking. A simple spark.read does none of that.

Answer First: Source data schemas change over time β€” someone adds a new column, renames a field, or changes a data type. Auto Loader can handle this automatically instead of breaking your pipeline.

Memory Map: Auto Loader handle schema evolution -> inference compares arriving fields with stored schema -> additive changes update metadata when policy allows -> incompatible data enters rescued storage -> schema history and alerts expose drift [04_ETL_Scenarios_and_Design.md:854].

Q15: How does Auto Loader handle schema evolution?

Simple Explanation: Source data schemas change over time β€” someone adds a new column, renames a field, or changes a data type. Auto Loader can handle this automatically instead of breaking your pipeline.

Analogy: Imagine you are receiving mail at your office. One day, the sender starts including a new "priority" field on every envelope. Auto Loader is like a smart mailroom clerk who notices the new field, updates the filing system to include it, and keeps processing β€” no human intervention needed.

Code:

# Schema evolution modes
df = spark.readStream.format("cloudFiles") \
.option("cloudFiles.format", "json") \
.option("cloudFiles.schemaLocation", "/schema/orders") \ # ← REQUIRED: stores the schema
.option("cloudFiles.schemaEvolutionMode", "addNewColumns") \ # ← How to handle new columns
.load("/landing/orders/")
ModeBehaviorUse Case
addNewColumnsAdds new columns, restarts streamSchema evolves frequently
rescueNew/unexpected columns go to _rescued_dataDon't want restarts
failOnNewColumnsPipeline fails when schema changesStrict schema control
noneIgnore new columnsFixed schema

Schema hints (force specific types):

.option("cloudFiles.schemaHints", "order_id LONG, amount DECIMAL(10,2)")
# ← Even if the source has order_id as a string, force it to LONG

Interview Tip: Mention rescue mode as the safest production option. Say "I use rescue mode so the pipeline never stops, and I monitor the _rescued_data column for unexpected schema changes."

What NOT to Say: "I use addNewColumns in production." It restarts the stream on schema change, which can cause brief downtime. Rescue mode is safer for 24/7 pipelines.

Answer First: Both ingest data from cloud storage into Delta tables. Auto Loader is streaming (continuous), COPY INTO is batch (run once or on a schedule). For most production use cases, Auto Loader is preferred.

Memory Map: Auto Loader vs COPY INTO β€” when do you use each -> notification-backed discovery supports continuous arrival -> idempotent SQL loading suits bounded batches -> checkpoint state favors the streaming option -> schedule and latency select the simpler path [04_ETL_Scenarios_and_Design.md:890].

Q16: Auto Loader vs COPY INTO β€” when do you use each?

Simple Explanation: Both ingest data from cloud storage into Delta tables. Auto Loader is streaming (continuous), COPY INTO is batch (run once or on a schedule). For most production use cases, Auto Loader is preferred.

Analogy:

  • Auto Loader = A conveyor belt in a factory. Items (files) are continuously picked up and processed as they arrive. The belt remembers what it has already moved.
  • COPY INTO = A forklift operator who picks up a batch of boxes, delivers them, and goes home. You schedule the forklift to come back tomorrow.

Comparison table:

AspectAuto LoaderCOPY INTO
Processing modelStreaming (incremental)Batch (one-time or scheduled)
State trackingCheckpoint-based (RocksDB)Table metadata (file list in _delta_log)
File discoveryEfficient (notification or incremental listing)Full directory listing every time
Schema evolutionBuilt-in (4 modes)Manual (you handle it)
ScaleHandles billions of filesSlower for millions of files
IdempotencyExactly-once via checkpointsExactly-once via file tracking
New file detectionAutomatic (event-driven or polling)Only when you run the command
Setup complexitySlightly more (stream + checkpoint)Simple SQL command
Best forProduction pipelines, continuous ingestionOne-time loads, simple batch jobs
RecommendationPreferred for most use casesSimple one-time loads, ad-hoc ingestion
sql
-- COPY INTO example (for comparison)
COPY INTO my_table
FROM '/landing/orders/'
FILEFORMAT = JSON
FORMAT_OPTIONS ('mergeSchema' = 'true')
COPY_OPTIONS ('mergeSchema' = 'true');
-- ← Simple, but no streaming, no auto-detection of new files

Interview Tip: Say "I default to Auto Loader for any production pipeline. I only use COPY INTO for one-time historical loads or ad-hoc data ingestion."

What NOT to Say: "COPY INTO and Auto Loader are the same thing." They are fundamentally different β€” streaming vs batch, checkpoint vs file tracking.

SECTION 5: LATE-ARRIVING DATA

Answer First: Late-arriving data is data that shows up after the processing window has already closed. In streaming, you use watermarks to define "how late is too late." Data within the watermark window gets processed normally.

Memory Map: you handle late-arriving data in streaming pipelines -> handle late arriving data in streaming pipelines sets the admissible correction or replay window -> arrival lag crosses the normal processing window -> replay or watermark policy selects admissible state -> affected outputs are corrected -> reconciliation proves eventual completeness [04_ETL_Scenarios_and_Design.md:932].

Q16b: How do you handle late-arriving data in streaming pipelines?

Simple Explanation: Late-arriving data is data that shows up after the processing window has already closed. In streaming, you use watermarks to define "how late is too late." Data within the watermark window gets processed normally. Data beyond the watermark gets dropped (or you design a reprocessing strategy).

Analogy: A student submitting homework after the teacher already graded. The teacher has a policy: "I accept late submissions up to 2 days after the deadline." After that, it is not accepted. The 2-day window = the watermark.

Timeline diagram:

πŸ“ Architecture Diagram
Timeline β†’  10:00   10:05   10:10   10:15   10:20   10:25   10:30
            ──────┬───────┬───────┬───────┬───────┬───────┬──────
Events:     β”‚ A   β”‚ B     β”‚ C     β”‚       β”‚ D*    β”‚       β”‚ E**
            β”‚10:00β”‚ 10:05 β”‚ 10:10 β”‚       β”‚ 10:02 β”‚       β”‚ 09:50

Watermark = 10 minutes

At 10:15: watermark = 10:15 - 10min = 10:05
  β†’ Event D* (event_time=10:02) arrives at 10:20
    Current watermark at 10:20 = 10:10
    10:02 < 10:10 β†’ D is WITHIN watermark β†’ ACCEPTED βœ“

  β†’ Event E** (event_time=09:50) arrives at 10:30
    Current watermark at 10:30 = 10:20
    09:50 < 10:20 β†’ E is BEYOND watermark β†’ DROPPED βœ—

Code:

# Watermark: accept data up to 10 minutes late
windowed = spark.readStream.format("delta") \
.table("bronze_events") \
.withWatermark("event_time", "10 minutes") \ # ← The "late homework policy"
.groupBy(
window("event_time", "5 minutes"), # ← 5-minute tumbling windows
"category"
).agg(count("*").alias("event_count"))
# For truly late data beyond the watermark, use a separate reprocessing job
# ← Run this daily to catch anything the streaming pipeline dropped
late_data = spark.read.table("bronze_events") \
.filter(col("event_time") < col("_ingested_at") - expr("INTERVAL 1 HOUR")) \
.filter(col("event_time") >= date_sub(current_date(), 1)) # ← Last 24 hours only
# Merge late data into Silver (batch)
target.alias("t").merge(
late_data.alias("s"),
"t.event_id = s.event_id"
).whenNotMatchedInsertAll().execute()

Interview Tip: Explain the tradeoff: "A longer watermark catches more late data but uses more memory. A shorter watermark is more efficient but drops more late events. In production, I set the watermark based on observed latency patterns and add a daily batch reprocessing job as a safety net."

What NOT to Say: "I set the watermark to infinity so nothing gets dropped." That defeats the purpose of watermarks and will eventually cause out-of-memory errors.

SECTION 6: DELTA LIVE TABLES (DLT)

Answer First: DLT expectations are data quality rules built into your pipeline. They run on every record and you choose what happens when a record violates the rule: log it, drop it, or fail the whole pipeline.

Memory Map: the three DLT expectation levels -> three dlt expectation levels chooses the declarative pipeline state and quality rule -> declared datasets form a dependency graph -> expectations enforce quality -> service advances incremental state -> event log records lineage and failures [04_ETL_Scenarios_and_Design.md:990].

Q17: What are the three DLT expectation levels?

Simple Explanation: DLT expectations are data quality rules built into your pipeline. They run on every record and you choose what happens when a record violates the rule: log it, drop it, or fail the whole pipeline.

Analogy: Airport security levels. Green lane (expect) = note the suspicious item but let the passenger through. Yellow lane (expect_or_drop) = confiscate the item and let the passenger through. Red lane (expect_or_fail) = shut down the entire terminal.

Code:

python β€” editable
import dlt

@dlt.table
@dlt.expect("valid_id", "order_id IS NOT NULL")                    # ← Log violation, keep row (green)
@dlt.expect_or_drop("positive_amount", "amount > 0")               # ← Drop violating rows (yellow)
@dlt.expect_or_fail("valid_status", "status IN ('A','B','C')")     # ← Fail entire pipeline (red)
def silver_orders():
    return dlt.read_stream("bronze_orders")
DecoratorOn ViolationUse Case
@dlt.expectRecords in metrics, keeps the rowMonitoring, soft quality
@dlt.expect_or_dropDrops the row silentlyFilter bad data
@dlt.expect_or_failFails the entire pipelineCritical data quality

Interview Tip: Mention that expectations produce metrics visible in the DLT UI. Say "I use expect for monitoring trends, expect_or_drop for known bad patterns, and expect_or_fail for data integrity constraints that must never be violated."

What NOT to Say: "I use expect_or_fail on everything." That makes your pipeline fragile. One bad record stops everything. Use it only for critical invariants.

Answer First: Three types of DLT datasets, each for a different purpose.

Memory Map: the difference between a streaming live table, a materialized view, and a view in DLT -> streaming tables incrementally consume new records -> materialized views persist refreshed query results -> ordinary views store only a definition -> freshness and ownership choose the object [04_ETL_Scenarios_and_Design.md:1021].

Q18: What is the difference between a streaming live table, a materialized view, and a view in DLT?

Simple Explanation: Three types of DLT datasets, each for a different purpose.

ConceptProcessingStorageUse Case
Streaming Live TableAppend-only, incrementalYes (Delta table)New data ingestion (Bronze, CDC)
Materialized ViewPrecomputed, incrementally updatedYes (Delta table)Aggregations, enriched data (Silver, Gold)
ViewVirtual, computed on readNo (just a query)Intermediate transformations, no storage

Analogy:

  • Streaming Live Table = A river that keeps flowing. New water (data) is always added, never removed.
  • Materialized View = A lake that gets refreshed. The lake level (aggregates) is recalculated when new water flows in.
  • View = A window overlooking the river. You see the data when you look, but nothing is stored at the window itself.
# Streaming Live Table (Bronze β€” append-only)
@dlt.table
def bronze_events():
return spark.readStream.format("cloudFiles").load(path) # ← Always appending new events
# Materialized View (Gold β€” automatically incremental)
@dlt.table
def gold_daily_metrics():
return dlt.read("silver_events") \ # ← Reads from Silver
.groupBy("date").agg(count("*").alias("event_count")) # ← Pre-aggregated
# View (intermediate, not materialized)
@dlt.view
def enriched_events():
return dlt.read("bronze_events").join(dim_table, "key") # ← Computed on-the-fly, not stored

Interview Tip: Know when to use each. Say "I use streaming live tables for Bronze ingestion, materialized views for Silver/Gold aggregations, and views for intermediate transformations that do not need to be persisted."

What NOT to Say: "They are all the same, just different syntax." They have fundamentally different processing semantics and storage behaviors.

Answer First: DLT's apply changes does in 5 lines what the manual merge-key trick does in 50+. It is the recommended approach for any new SCD Type 2 implementation.

Memory Map: you implement SCD Type 2 in DLT? (Compare with manual approach) -> managed change application declares keys and ordering -> service maintains historical intervals automatically -> manual SQL exposes staging and clause control -> complexity and customization choose the approach [04_ETL_Scenarios_and_Design.md:1061].

Q19: How do you implement SCD Type 2 in DLT? (Compare with manual approach)

Simple Explanation: DLT's apply_changes does in 5 lines what the manual merge-key trick does in 50+. It is the recommended approach for any new SCD Type 2 implementation.

Code:

python β€” editable
# DLT approach β€” 5 lines vs 50+ lines manually
dlt.create_streaming_table("dim_customer_scd2")       # ← Create the target

dlt.apply_changes(
    target="dim_customer_scd2",                        # ← Where to write
    source="bronze_customer_cdc",                      # ← Where to read
    keys=["customer_id"],                              # ← Business key
    sequence_by=col("updated_at"),                     # ← Ordering column (handles out-of-order!)
    apply_as_deletes=expr("operation = 'DELETE'"),     # ← Delete condition
    except_column_list=["operation", "_rescued_data"], # ← Columns to exclude
    stored_as_scd_type=2                               # ← SCD Type 2 β€” one line!
)
# DLT auto-manages: __START_AT, __END_AT, __IS_CURRENT columns

Why DLT is better for SCD Type 2:

  • No merge-key trick needed
  • Handles out-of-order events automatically
  • Handles deletes natively
  • Incremental processing built-in
  • Data quality expectations on the same pipeline

Interview Tip: Show both approaches. Say "I know the manual merge-key approach (explain it), but in production I use DLT's apply_changes because it handles edge cases like out-of-order events that are painful to implement manually."

What NOT to Say: "I only use the manual approach because I do not trust DLT." DLT is a first-class Databricks feature and the recommended approach for production SCD Type 2.

SECTION 7: SCENARIO-BASED PIPELINE DESIGN

Answer First: This is a scale + architecture question. 10B events/day = ~115K events/second. You need streaming ingestion, efficient processing, and careful resource planning.

Memory Map: Scenario β€” Design a pipeline for 10 billion clickstream events/day from Kafka -> Kafka partitions absorb clickstream throughput -> event-time processing bounds late data -> medallion layers isolate replay and quality -> lag and reconciliation metrics enforce daily completeness [04_ETL_Scenarios_and_Design.md:1098].

Q20: Scenario β€” Design a pipeline for 10 billion clickstream events/day from Kafka.

Simple Explanation: This is a scale + architecture question. 10B events/day = ~115K events/second. You need streaming ingestion, efficient processing, and careful resource planning.

Answer:

πŸ—‚οΈDesign:
Event rate: 10B/day = ~115K events/second
Source: Kafka (multiple topics, partitioned by user_id hash)
Bronze:
Structured Streaming from Kafka
Parse JSON, add ingestion metadata
Append-only, partitioned by ingestion_hour
Trigger: processingTime="30 seconds"
Cluster: 16 workers, Photon enabled
Expected volume: ~2 TB/day compressed
Silver:
Read from Bronze with CDF or streaming
Deduplicate (dropDuplicates with watermark)
Parse user agent, extract session info
Join with dim_user (broadcast β€” ~10 GB)
Sessionization (gap-based, 30-min timeout)
Write to silver_sessions table
CLUSTER BY (user_id)
Gold:
gold_hourly_metrics: page views, unique users per hour
gold_session_metrics: avg session duration, pages per session
gold_funnel_analysis: conversion funnel drop-offs
Trigger: availableNow (scheduled every hour)
Monitoring:
Streaming query listener for lag alerts
Row count reconciliation: Kafka offset vs Bronze count
Processing rate dashboard
Alert if processing time > 2x average

Interview Tip: Walk through the math first. "10B/day = 115K/sec = ~2 TB/day. That tells me I need 16+ workers for Bronze ingestion alone." Showing you can estimate cluster sizing is a senior skill.

What NOT to Say: "I would just use a bigger cluster." Without estimating the actual throughput, this sounds like you are throwing money at the problem.

Answer First: Interview Tip: Start with profiling, not solutions. Say "The first thing I would do is open the Spark UI and identify which stage takes the longest. Then I would apply targeted optimizations." This shows methodical thinking.

Memory Map: Scenario β€” Your daily batch pipeline takes 8 hours. Business wants it under 2 hours. How do you optimize -> daily batch pipeline takes 8 hours business wants it under 2 hours optimize isolates the relevant fragmentation or maintenance cause -> file-count baseline exposes fragmentation -> compaction policy selects candidate files -> rewritten layout reduces overhead -> operation metrics and scan time prove benefit [04_ETL_Scenarios_and_Design.md:1146].

Q21: Scenario β€” Your daily batch pipeline takes 8 hours. Business wants it under 2 hours. How do you optimize?

Answer:

πŸ—‚οΈDebugging Methodology:
1. PROFILE: Check Spark UI for every stage
Which stage is slowest? (joins, aggregations, writes)
Is there data skew? (1 task >> others)
Is there spill to disk?
What join strategies are used?
2. IDENTIFY BOTTLENECKS (common findings):
Shuffle: Too many/few partitions
Skew: Uneven data distribution
Small files: Reading 100K tiny files
Full table scans: No partition pruning
Python UDFs: Serialization overhead
3. OPTIMIZATION STRATEGIES:
a. Switch to incremental processing
Process only changed data (use CDF, Auto Loader)
b. Optimize joins
Broadcast small tables
Salt skewed keys
Bucket tables that join repeatedly
c. Enable AQE for automatic optimizations
d. Use Photon runtime (up to 12x faster)
e. OPTIMIZE + ZORDER target tables
f. Increase cluster size (scale out)
g. Replace Python UDFs with built-in functions or Pandas UDFs
h. Parallelize independent stages (DAG, not sequential)
i. Use replaceWhere for partition-level overwrites
j. Enable optimized writes and auto-compaction

Interview Tip: Start with profiling, not solutions. Say "The first thing I would do is open the Spark UI and identify which stage takes the longest. Then I would apply targeted optimizations." This shows methodical thinking.

What NOT to Say: "Just throw more machines at it." Scaling out does not fix data skew, bad join strategies, or Python UDF overhead.

Answer First: Interview Tip: Data mesh is an organizational pattern, not a technology. Emphasize ownership and governance, not just the tools.

Memory Map: Scenario β€” Design a data mesh on Databricks -> business domains own named data products -> Unity Catalog defines discoverable contracts -> shared platform supplies policy and observability -> consumer SLAs measure domain accountability [04_ETL_Scenarios_and_Design.md:1187].

Q22: Scenario β€” Design a data mesh on Databricks.

Answer:

πŸ—‚οΈPrinciples:
1. Domain-oriented data ownership
2. Data as a product
3. Self-serve data platform
4. Federated computational governance
Implementation:
Unity Catalog Structure:
catalog: marketing (owned by marketing team)
schema: bronze (raw marketing data)
schema: silver (cleansed)
schema: gold (marketing metrics β€” the "product")
catalog: sales (owned by sales team)
... (same pattern)
catalog: shared (cross-domain data products)
schema: customer_360 (shared customer view)
Governance:
Unity Catalog for centralized access control
Each domain defines their own data quality SLAs
Shared data contracts (schema + SLA + ownership)
Audit logging for compliance
Self-Serve Platform:
Databricks Asset Bundles for CI/CD templates
Cluster policies per domain (cost control)
Shared notebook templates and libraries
Data quality framework (DLT expectations)
Data Products:
Each domain publishes curated Gold tables
Cross-domain access via Unity Catalog GRANT
Delta Sharing for external consumers
SLA monitoring with alerts

Interview Tip: Data mesh is an organizational pattern, not a technology. Emphasize ownership and governance, not just the tools.

What NOT to Say: "Data mesh means every team runs their own Databricks workspace." That misses the point. Data mesh is about ownership and interoperability, not isolation.

Answer First: Interview Tip: Mention the latency requirement. "For fraud detection, we need sub-second scoring. I use foreachBatch with a 10-second trigger, and the model is loaded once per batch, not per row.".

Memory Map: Scenario β€” Design a real-time fraud detection pipeline -> streaming features join transactions with recent behavior -> low-latency model scores each event -> decision service routes suspicious payments -> labeled outcomes monitor drift and false positives [04_ETL_Scenarios_and_Design.md:1233].

Q23: Scenario β€” Design a real-time fraud detection pipeline.

Answer:

# Architecture:
# Transaction stream (Kafka) β†’ Bronze β†’ Feature Engineering β†’ ML Scoring β†’ Alert
# 1. Bronze: Raw transactions
transactions = spark.readStream.format("kafka") \
.option("subscribe", "transactions") \
.load() \
.select(from_json(col("value").cast("string"), txn_schema).alias("txn")) \
.select("txn.*")
# 2. Feature Engineering: Rolling aggregates
w_1h = Window.partitionBy("card_id").orderBy("event_time").rangeBetween(-3600, 0)
w_24h = Window.partitionBy("card_id").orderBy("event_time").rangeBetween(-86400, 0)
features = transactions \
.withWatermark("event_time", "5 minutes") \
.withColumn("txn_count_1h", count("*").over(w_1h)) \ # ← Transactions in last hour
.withColumn("total_amount_1h", sum("amount").over(w_1h)) \ # ← Total spend in last hour
.withColumn("txn_count_24h", count("*").over(w_24h)) \ # ← Transactions in last 24h
.withColumn("avg_amount_24h", avg("amount").over(w_24h)) \ # ← Average spend in last 24h
.withColumn("amount_deviation", (col("amount") - col("avg_amount_24h")) / col("avg_amount_24h"))
# 3. ML Scoring (using foreachBatch for model inference)
def score_and_alert(batch_df, batch_id):
import mlflow
model = mlflow.pyfunc.load_model("models:/fraud_model/Production") # ← Load registered model
# Score
scored = batch_df.toPandas()
scored["fraud_score"] = model.predict(scored[feature_cols])
scored_df = spark.createDataFrame(scored)
# Write all scored transactions
scored_df.write.format("delta").mode("append").saveAsTable("silver_scored_transactions")
# Alert on high-risk
alerts = scored_df.filter(col("fraud_score") > 0.8) # ← High confidence fraud
alerts.write.format("delta").mode("append").saveAsTable("gold_fraud_alerts")
# Trigger webhook for real-time blocking
if alerts.count() > 0:
send_webhook(alerts.collect())
features.writeStream.foreachBatch(score_and_alert) \
.option("checkpointLocation", "/checkpoints/fraud") \
.trigger(processingTime="10 seconds") \ # ← Score every 10 seconds
.start()

Interview Tip: Mention the latency requirement. "For fraud detection, we need sub-second scoring. I use foreachBatch with a 10-second trigger, and the model is loaded once per batch, not per row."

What NOT to Say: "I would run a batch job every hour to detect fraud." By then the fraudulent transaction is already completed. Fraud detection must be near real-time.

Answer First: This is a two-phase problem: (1) Load 2 years of history without blowing up your cluster, (2) Seamlessly switch to incremental processing going forward.

Memory Map: Scenario β€” You need to backfill 2 years of historical data, then switch to incremental. Design this -> historical files load through bounded parallel batches -> watermark-free reconciliation establishes the baseline -> checkpoint starts after the backfill boundary -> incremental ingestion continues without overlap [04_ETL_Scenarios_and_Design.md:1293].

Q24: Scenario β€” You need to backfill 2 years of historical data, then switch to incremental. Design this.

Simple Explanation: This is a two-phase problem: (1) Load 2 years of history without blowing up your cluster, (2) Seamlessly switch to incremental processing going forward.

Analogy: Moving into a new house. Phase 1: bring all your old furniture (historical backfill) β€” you do it room by room, not all at once. Phase 2: set up mail forwarding (incremental) so all future deliveries go to the new address.

Code:

# Phase 1: Historical Backfill (batch)
# ← Process month by month to manage memory (don't try to load 2 years at once!)
from datetime import datetime, timedelta
from dateutil.relativedelta import relativedelta
start_date = datetime(2023, 1, 1)
end_date = datetime(2025, 1, 1)
current = start_date
while current < end_date:
next_month = current + relativedelta(months=1)
# Read one month at a time
month_df = spark.read.parquet(f"/raw/data/year={current.year}/month={current.month:02d}/")
# Transform
transformed = apply_transformations(month_df)
# Write idempotently using replaceWhere
# ← If the job fails and reruns, it overwrites the same partition β€” no duplicates!
transformed.write.format("delta") \
.mode("overwrite") \
.option("replaceWhere", f"year = {current.year} AND month = {current.month}") \
.saveAsTable("silver_table")
current = next_month
# Optimize after backfill (the table is now fragmented from 24 separate writes)
spark.sql("OPTIMIZE silver_table ZORDER BY (customer_id)")
# Phase 2: Switch to Incremental (streaming)
# ← From now on, Auto Loader picks up new files automatically
spark.readStream.format("cloudFiles") \
.option("cloudFiles.format", "parquet") \
.load("/raw/data/") \ # ← Same source folder
.writeStream.format("delta") \
.option("checkpointLocation", "/checkpoints/silver") \
.trigger(availableNow=True) \ # ← Run as scheduled batch (not continuous)
.table("silver_table")

Interview Tip: Emphasize idempotency. Say "I use replaceWhere so if any monthly batch fails, I can safely rerun it without creating duplicates. And I OPTIMIZE after backfill because 24 separate writes create fragmented files."

What NOT to Say: "I would load all 2 years at once." That will likely OOM your cluster or take days. Month-by-month is the production approach.

Delta integration summary β€” Q25: Scenario β€” Your MERGE statement takes 3 hours. How do you optimize it?: Databricks jobs invoke Delta MERGE, but scan pruning, source deduplication, file layout, and operation-metric diagnosis belong to the canonical Delta optimization answer. Open the canonical Delta answer.

Answer First: Publish one governed table and expose team-specific rows or columns through Unity Catalog dynamic views, row filters, or column masks. Central grants and audit logs prevent separate copies from drifting.

Memory Map: Scenario β€” Multiple teams need access to the same data with different views. How -> canonical table preserves shared facts -> governed views apply team-specific rows and columns -> group grants bind access policies -> impersonation tests verify each audience [04_ETL_Scenarios_and_Design.md:1397].

Q26: Scenario β€” Multiple teams need access to the same data with different views. How?

Answer: Using Unity Catalog's governance features:

sql
-- Base table accessible to data engineering
CREATE TABLE catalog.schema.customer_data (...);

-- Marketing team: sees masked PII
CREATE FUNCTION mask_email(email STRING)                    -- # ← Dynamic masking function
RETURN CASE
    WHEN is_member('data_engineering') THEN email           -- # ← DE team sees real email
    ELSE regexp_replace(email, '(.).+(@.+)', '$1***$2')     -- # ← Everyone else sees a***@gmail.com
END;

ALTER TABLE customer_data ALTER COLUMN email SET MASK mask_email;  -- # ← Apply mask to column

-- Sales team: only sees their region
CREATE FUNCTION region_filter(region STRING)                -- # ← Row-level security function
RETURN region = current_user_region() OR is_member('admin');

ALTER TABLE customer_data SET ROW FILTER region_filter ON (region);  -- # ← Apply row filter

-- Analytics team: aggregated gold view (no PII at all)
CREATE VIEW gold_customer_metrics AS
SELECT region, COUNT(*) as customers, AVG(lifetime_value) as avg_ltv
FROM customer_data
GROUP BY region;

Interview Tip: Mention all three layers of security: column masking (hide sensitive values), row filtering (show only relevant rows), and views (pre-aggregated, no PII). This shows defense-in-depth thinking.

What NOT to Say: "I would create separate copies of the table for each team." That creates data duplication, inconsistency, and governance nightmares.

Answer First: An idempotent pipeline produces the same result no matter how many times you run it. This is critical because pipelines fail and need to be rerun. If rerunning creates duplicates or overwrites good data, you have a serious problem.

Memory Map: you design an idempotent pipeline -> stable event identity defines duplicate scope -> deterministic merge prevents repeated effects -> checkpoint and transaction IDs record progress -> replay test produces unchanged target state [04_ETL_Scenarios_and_Design.md:1433].

Q27: How do you design an idempotent pipeline?

Simple Explanation: An idempotent pipeline produces the same result no matter how many times you run it. This is critical because pipelines fail and need to be rerun. If rerunning creates duplicates or overwrites good data, you have a serious problem.

Analogy: Pressing an elevator button multiple times. The elevator comes once, regardless of how many times you press. That is idempotency.

Code:

python β€” editable
# Principle: Re-running the pipeline produces the SAME result

# 1. Use MERGE instead of INSERT (handles reruns)
# ← If the record already exists, it updates instead of creating a duplicate
target.alias("t").merge(source.alias("s"), "t.id = s.id") \
    .whenMatchedUpdateAll().whenNotMatchedInsertAll().execute()

# 2. Use replaceWhere for partition-level overwrites
# ← Re-running replaces the same partition β€” no duplicates
df.write.format("delta").mode("overwrite") \
    .option("replaceWhere", "date = '2025-01-15'") \
    .saveAsTable("orders")

# 3. Streaming: checkpoints ensure exactly-once
# ← Re-starting reads from last committed offset β€” no reprocessing
stream.writeStream.option("checkpointLocation", "/checkpoints/job1").start()

# 4. Auto Loader: tracks processed files
# ← Re-starting skips already-processed files
spark.readStream.format("cloudFiles").load(path)

# 5. Batch jobs: use run_date parameter
# ← Each run is scoped to a specific date β€” safe to rerun
def process_day(run_date):
    df = spark.read.parquet(f"/raw/{run_date}/")
    df.write.format("delta").mode("overwrite") \
        .option("replaceWhere", f"process_date = '{run_date}'") \
        .saveAsTable("silver_table")

Interview Tip: Name the five patterns: MERGE, replaceWhere, checkpoints, Auto Loader file tracking, and date-scoped batch jobs. This shows you have a toolkit, not just one trick.

What NOT to Say: "I use INSERT with mode append." That creates duplicates on every rerun. Append is NOT idempotent unless paired with deduplication.

Answer First: Production monitoring must cover data quality, freshness, and pipeline execution health. Together, those signals distinguish bad content, stale content, and failed processing.

Memory Map: you monitor pipeline health in production -> monitor pipeline health in production chooses the first evidence branch for diagnosis -> first causal signal anchors triage -> plan, logs, and metrics isolate one cause -> smallest safe change addresses it -> same evidence verifies recovery [04_ETL_Scenarios_and_Design.md:1478].

Q28: How do you monitor pipeline health in production?

Simple Explanation: Production pipelines need three types of monitoring: data quality (is the data correct?), data freshness (is the data recent?), and pipeline health (is the pipeline running?).

Code:

python β€” editable
# 1. Row count reconciliation (data completeness)
# ← Compare counts across layers to detect data loss
source_count = spark.read.table("bronze_orders").filter("date = '2025-01-15'").count()
silver_count = spark.read.table("silver_orders").filter("date = '2025-01-15'").count()
gold_count = spark.read.table("gold_daily_orders").filter("date = '2025-01-15'").count()

assert abs(source_count - silver_count) / source_count < 0.01, "Silver count drift > 1%"

# 2. Data freshness SLA (data timeliness)
# ← Alert if data is stale beyond your SLA
from pyspark.sql.functions import max as _max, current_timestamp, unix_timestamp

freshness = spark.table("silver_orders") \
    .select(
        (unix_timestamp(current_timestamp()) - unix_timestamp(_max("updated_at"))) / 60
    ).collect()[0][0]  # ← minutes since last update

assert freshness < 60, f"Data is {freshness} minutes stale!"

# 3. Streaming metrics (pipeline health)
# ← Monitor input rate vs processing rate
for query in spark.streams.active:
    progress = query.lastProgress
    print(f"Input rate: {progress['inputRowsPerSecond']}")
    print(f"Processing rate: {progress['processedRowsPerSecond']}")
    print(f"Batch duration: {progress['batchDuration']}")
    # Alert if processing rate < input rate (falling behind!)

# 4. DLT expectation metrics (available in DLT event log)
expectations = spark.read.table("event_log") \
    .filter("event_type = 'flow_progress'") \
    .select("timestamp", "details:flow_progress:data_quality:*")

Interview Tip: Mention all three: quality, freshness, and health. Say "I set up alerts for count drift > 1%, freshness > SLA threshold, and streaming lag. Most production incidents are caught by freshness alerts first."

What NOT to Say: "I check the pipeline manually every morning." That is not monitoring, that is hoping. Automated alerts are non-negotiable for production pipelines.

ETL Patterns & Pipeline Design

πŸ’‘ Interview Tip
Time: 6-7 hours | Priority: HIGH β€” ETL design is 25-30% of Databricks interviews Context: Oracle CDC β†’ Kafka β†’ Delta Lake, flight booking pipelines, fare pricing updates Approach: Every topic starts with simple explanation β†’ then interview-level depth

SECTION 1: MEDALLION ARCHITECTURE (1 hour)

Answer First: Medallion Architecture is a way to organize your data into 3 layers (like 3 levels of cleaning). Raw data comes in, gets cleaned step by step, and ends up ready for business reports and ML.

Memory Map: Medallion Architecture -> bronze preserves source fidelity and replay metadata -> silver standardizes keys schema and quality -> gold publishes business aggregates -> layer-level reconciliation gates promotion [DB_02_ETL_Pipelines_Databricks.md:11].

Q1: What is Medallion Architecture?

Simple Explanation: Medallion Architecture is a way to organize your data into 3 layers (like 3 levels of cleaning). Raw data comes in, gets cleaned step by step, and ends up ready for business reports and ML.

Think of it like cooking:

  • Bronze = Raw ingredients from the market (dirty, uncut, as-is)
  • Silver = Washed, cut, measured ingredients (clean, validated, ready to cook)
  • Gold = Final dish served to the customer (aggregated, formatted for business use)

Why do we need it? Without layers, you'd have a mess β€” raw data mixed with cleaned data, no one knows what's trustworthy. Medallion gives you a clear pipeline: raw β†’ clean β†’ business-ready.

BRONZE (Raw Layer) SILVER (Clean Layer) GOLD (Business Layer)
────────────────── ───────────────── ─────────────────
What goes in: What goes in: What goes in:
β€’ Raw data exactly as β€’ Deduplicated data β€’ Aggregated metrics
received from source β€’ Validated (nulls removed) β€’ Star schema for BI
β€’ No transformations β€’ Data types fixed β€’ Pre-computed KPIs
β€’ Just append, never update β€’ Business keys applied β€’ Wide tables for ML
Who reads it: Who reads it: Who reads it:
β€’ Only data engineers β€’ Data engineers + β€’ Business analysts
(for debugging) data scientists β€’ BI tools (Power BI)
β€’ ML models
Metadata added: Transformations: Optimized for:
β€’ _ingested_at (when) β€’ MERGE (upsert) β€’ Fast queries
β€’ _source_file (from where) β€’ SCD Type 2 for dimensions β€’ Self-service analytics
β€’ _batch_id (which batch) β€’ PII masking/tagging β€’ Dashboard performance

Design decisions per layer (Example):

DecisionBronzeSilverGold
FormatDelta (store raw JSON/CSV as Delta)DeltaDelta
ClusteringBy ingestion dateBy business key (booking_id)By query pattern (airport, date)
Quality checksSchema validation onlyNull checks, range checks, dedupBusiness rule validation
Retention90 days raw7 years (regulatory for travel)Latest + 2 years
Who readsData engineers onlyEngineers + scientistsAnalysts + BI + ML
Update patternAppend only (never update)MERGE (upsert)Overwrite partition or MERGE

Interview tip: Always explain Medallion with a real example: "In our booking pipeline, Bronze stores raw airline JSON as-is, Silver cleans and deduplicates bookings with MERGE, and Gold has daily revenue aggregations that Power BI reads."

Answer First: Medallion is a guideline, not a strict rule. Sometimes you skip layers or add extra layers.

Memory Map: When would you NOT follow Medallion Architecture -> simple sources may not justify three persisted copies -> low-latency paths can bypass unnecessary refinement -> extra regulatory stages may add controls -> explicit contracts preserve accountability [DB_02_ETL_Pipelines_Databricks.md:59].

Q2: When would you NOT follow Medallion Architecture?

Simple Explanation: Medallion is a guideline, not a strict rule. Sometimes you skip layers or add extra layers:

  1. Real-time dashboards: Data is already clean β†’ read directly from Silver (skip Gold)
  2. Simple data: Source is already clean β†’ Bronze directly to Gold (skip Silver)
  3. ML features: Add a "Feature" layer between Silver and Gold for ML feature tables
  4. Data Mesh: Each team (bookings team, flights team) owns their own Bronze→Silver→Gold
  5. Reverse ETL: Gold data pushed back to external systems (CRM, marketing tools)

Interview tip: When they ask "When would you deviate?", say: "It depends on the use case. For our real-time flight status dashboard, we skip Gold and read directly from Silver with materialized views β€” adding Gold would add unnecessary latency."

Answer First: In real life, data doesn't always arrive on time. A booking created on March 1 might reach your pipeline on March 5 (airline system was down, or batch was delayed).

Memory Map: you handle late-arriving data -> event time separates occurrence from ingestion -> watermark defines accepted delay -> correction or replay updates affected outputs -> completeness metrics quantify late impact [DB_02_ETL_Pipelines_Databricks.md:74].

Q3: How do you handle late-arriving data?

Simple Explanation: In real life, data doesn't always arrive on time. A booking created on March 1 might reach your pipeline on March 5 (airline system was down, or batch was delayed). This is called late-arriving data. Each layer handles it differently.

Example scenario: An airline's system was offline for 4 days. When it comes back, it sends 4 days of bookings at once.

sql
-- BRONZE: No problem! Bronze is append-only.
-- Late data just gets appended with _ingested_at = March 5 (when it actually arrived).
-- We keep both the actual booking_date (March 1) and _ingested_at (March 5).

-- SILVER: MERGE handles late data naturally!
MERGE INTO silver.bookings t              -- t = existing clean bookings
USING bronze.bookings_new s               -- s = new data (includes late-arriving)
ON t.booking_id = s.booking_id            -- Match by booking ID

WHEN MATCHED AND s.updated_at > t.updated_at THEN
    UPDATE SET *                           -- Update only if new data is newer
    -- Late data with older updated_at won't overwrite newer records

WHEN NOT MATCHED THEN
    INSERT *                               -- Brand new bookings get inserted
    -- Late bookings that were never seen before get added
# GOLD: Reprocess affected date partitions
# Since late data from March 1 arrived on March 5,
# we need to recompute Gold aggregations for March 1-5
df.write.format("delta") \
.mode("overwrite") \
.option("replaceWhere", "booking_date >= '2026-03-01'") \ # Only rewrite March dates
.saveAsTable("gold.daily_bookings")
# replaceWhere = "only overwrite these specific partitions, leave everything else untouched"

SECTION 2: SCD IMPLEMENTATIONS (1.5 hours)

Q4: What is SCD (Slowly Changing Dimension)?

Simple Explanation: In any database, some data changes over time. A passenger's address changes when they move. A loyalty tier goes from Silver to Gold. The question is: how do you handle these changes?

SCD = Slowly Changing Dimension β€” it's a set of techniques for handling data that changes over time in your dimension tables (like passenger, product, customer tables).

Real-world analogy: A phone contact changes their number. Do you:

  • Type 1: Just overwrite the old number (you lose the old number forever)
  • Type 2: Keep both numbers with dates ("old number: until March 2026, new number: from March 2026")
  • Type 3: Add a column "previous_number" (keeps only ONE previous value)
TypeWhat It DoesDo You Keep History?Travel Example
Type 0Never update β€” data is staticNoAirport codes (BLR, DEL, JFK never change)
Type 1Overwrite old value with newNo β€” old value is lostCorrecting a typo in passenger name
Type 2Add new row, close old rowFull historyPassenger changes address β†’ keep both old and new
Type 3Add "previous value" columnOnly 1 previousLoyalty tier: current=Gold, previous=Silver
Type 6Hybrid of 1+2+3Full + current + previousComplex regulatory dimensions

Interview tip: SCD Type 2 is the #1 most asked coding question in Databricks interviews. Know it by heart.

Answer First: SCD Type 2 preserves full history: expire the current row by setting its end date and current flag, then insert a new current row with the changed values.

Memory Map: SCD Type 2 β€” FULL IMPLEMENTATION (The #1 most asked question) -> staged source deduplicates business keys -> matched current row closes its validity interval -> unmatched staged row inserts the successor version -> interval and current-row assertions verify history [DB_02_ETL_Pipelines_Databricks.md:139].

Q5: SCD Type 2 β€” FULL IMPLEMENTATION (The #1 most asked question)

Simple Explanation: SCD Type 2 keeps full history of changes. When a passenger changes their address, we DON'T overwrite the old row. Instead, we:

  1. Mark the old row as "no longer current" (set is_current = FALSE, set effective_end = now)
  2. Insert a NEW row with the new address (set is_current = TRUE, set effective_end = 9999-12-31)

This way, you can always answer: "What was this passenger's address on January 15, 2025?"

Example: Passenger moves from Bangalore to Mumbai:

surrogate_keypassenger_idaddressis_currenteffective_starteffective_end
1PAX-100BangaloreTRUE2024-01-019999-12-31
surrogate_keypassenger_idaddressis_currenteffective_starteffective_end
1PAX-100BangaloreFALSE2024-01-012026-03-24
2PAX-100MumbaiTRUE2026-03-249999-12-31

Table structure:

sql
CREATE TABLE dim_passenger (
    surrogate_key BIGINT GENERATED ALWAYS AS IDENTITY,
    -- surrogate_key: Auto-generated unique ID for each row (not the business key)
    -- Why? Because passenger_id can have multiple rows (one per version)

    passenger_id STRING,           -- Business key (the real passenger ID from source system)
    full_name STRING,
    email STRING,
    address STRING,
    loyalty_tier STRING,

    hash_value STRING,             -- MD5 hash of all tracked columns (name + email + address + loyalty)
    -- Why hash? To quickly check if anything changed. Compare hash instead of comparing every column.

    effective_start TIMESTAMP,     -- When this version became active
    effective_end TIMESTAMP,       -- When this version was replaced (9999-12-31 = still current)
    is_current BOOLEAN             -- TRUE = this is the latest version of this passenger
) USING DELTA
CLUSTER BY (passenger_id);         -- Cluster for fast lookups by passenger_id

The Merge Key Trick β€” this is the clever part:

The challenge: In a single MERGE, we need to do TWO things:

  1. UPDATE the old row (close it: is_current = FALSE)
  2. INSERT the new row (new version: is_current = TRUE)

But MERGE's WHEN MATCHED only does updates, and WHEN NOT MATCHED only does inserts. How do we do both?

The trick: Create TWO rows for each changed passenger in the source:

  • Row 1: merge_key = passenger_id β†’ This MATCHES the target β†’ triggers UPDATE (closes old row)
  • Row 2: merge_key = NULL β†’ NULL never matches anything β†’ triggers NOT MATCHED β†’ INSERT (new row)
sql
-- Step 1: Prepare two rows per changed passenger
WITH staged_changes AS (

    -- === ROW TYPE A: INSERT new current version ===
    -- merge_key = NULL β†’ will NOT match target β†’ goes to WHEN NOT MATCHED β†’ INSERT
    SELECT
        s.passenger_id,
        s.full_name,
        s.email,
        s.address,
        s.loyalty_tier,
        MD5(CONCAT_WS('||', s.full_name, s.email, s.address, s.loyalty_tier)) AS hash_value,
        -- MD5 creates a hash of all tracked columns
        -- If any column changes, the hash changes β†’ we know something changed
        NULL AS merge_key
        -- NULL merge_key = this row will NEVER match target
        -- So it goes to WHEN NOT MATCHED β†’ INSERT as new current row
    FROM staging_passenger s
    JOIN dim_passenger t
        ON s.passenger_id = t.passenger_id    -- Find existing passenger
        AND t.is_current = TRUE                -- Only compare with current version
        AND MD5(CONCAT_WS('||', s.full_name, s.email, s.address, s.loyalty_tier)) != t.hash_value
        -- Only include passengers whose data actually CHANGED (hash is different)

    UNION ALL

    -- === ROW TYPE B: CLOSE the old version ===
    -- merge_key = passenger_id β†’ WILL match target β†’ goes to WHEN MATCHED β†’ UPDATE
    SELECT
        s.passenger_id,
        s.full_name,
        s.email,
        s.address,
        s.loyalty_tier,
        MD5(CONCAT_WS('||', s.full_name, s.email, s.address, s.loyalty_tier)) AS hash_value,
        s.passenger_id AS merge_key
        -- Real passenger_id as merge_key = this row WILL match target
        -- So it goes to WHEN MATCHED β†’ UPDATE (close the old row)
    FROM staging_passenger s
    JOIN dim_passenger t
        ON s.passenger_id = t.passenger_id
        AND t.is_current = TRUE
        AND MD5(CONCAT_WS('||', s.full_name, s.email, s.address, s.loyalty_tier)) != t.hash_value

    UNION ALL

    -- === ROW TYPE C: Brand new passengers (never seen before) ===
    -- merge_key = NULL β†’ WHEN NOT MATCHED β†’ INSERT
    SELECT
        s.passenger_id,
        s.full_name,
        s.email,
        s.address,
        s.loyalty_tier,
        MD5(CONCAT_WS('||', s.full_name, s.email, s.address, s.loyalty_tier)) AS hash_value,
        NULL AS merge_key              -- New passenger β†’ insert
    FROM staging_passenger s
    LEFT JOIN dim_passenger t
        ON s.passenger_id = t.passenger_id AND t.is_current = TRUE
    WHERE t.passenger_id IS NULL       -- Not found in target β†’ brand new passenger
)

-- Step 2: MERGE does both close + insert in ONE atomic operation
MERGE INTO dim_passenger AS target
USING staged_changes AS source
ON target.passenger_id = source.merge_key AND target.is_current = TRUE
-- Match condition uses merge_key (not passenger_id!)
-- Row Type B (merge_key = real ID) β†’ matches β†’ WHEN MATCHED
-- Row Type A & C (merge_key = NULL) β†’ never matches β†’ WHEN NOT MATCHED

-- Close the old record (Row Type B triggered this)
WHEN MATCHED THEN UPDATE SET
    target.is_current = FALSE,                    -- Mark as no longer current
    target.effective_end = current_timestamp()     -- Record when it was closed

-- Insert new current record (Row Type A and C triggered this)
WHEN NOT MATCHED THEN INSERT (
    passenger_id, full_name, email, address, loyalty_tier,
    hash_value, effective_start, effective_end, is_current
) VALUES (
    source.passenger_id, source.full_name, source.email, source.address,
    source.loyalty_tier, source.hash_value,
    current_timestamp(),                          -- This version starts NOW
    CAST('9999-12-31' AS TIMESTAMP),              -- Far future = "no end date yet"
    TRUE                                          -- This is the current version
);

Summary of the trick:

Row Typemerge_keyWhat HappensAction
A (changed passenger - new row)NULLDoesn't match targetINSERT new current version
B (changed passenger - close old)Real IDMatches targetUPDATE: close old row
C (brand new passenger)NULLDoesn't match targetINSERT first version

Interview tip: Practice writing this from memory. Draw the "before/after" table on paper to visualize. The merge_key trick is the key insight β€” be ready to explain WHY we use NULL.

Q6: SCD Type 2 the EASY way β€” Lakeflow apply_changes

Simple Explanation: The merge_key trick above is complex. Databricks has a MUCH easier way to do SCD Type 2 using Lakeflow Declarative Pipelines (formerly DLT). One function call β€” apply_changes() β€” does everything automatically.

When to use which:

  • Merge key trick: When you need full control, or when not using Lakeflow
  • apply_changes: When using Lakeflow pipelines (recommended β€” simpler and less error-prone)
import dlt # This is the Lakeflow (formerly DLT) library
# Step 1: Define the source β€” where new passenger data comes from
@dlt.table # @dlt.table = "this is a Lakeflow table"
def staging_passenger():
return spark.readStream \ # readStream = read data as a stream (continuously)
.format("delta") \ # Source is a Delta table
.table("bronze.passenger_events") # Read from Bronze layer
# Step 2: Create the SCD Type 2 dimension table β€” just ONE function call!
dlt.create_streaming_table("dim_passenger") # Create empty streaming table
dlt.apply_changes(
target="dim_passenger", # Where to write (target dimension table)
source="staging_passenger", # Where to read from (staging table above)
keys=["passenger_id"], # Business key β€” how to identify a passenger
sequence_by="updated_at", # How to order events β€” latest wins
stored_as_scd_type=2, # Tell Lakeflow: "use SCD Type 2 pattern"
track_history_column_list=[ # Which columns to watch for changes
"full_name", "email", "address", "loyalty_tier"
]
# If any of these columns change, a new version is created
)
# That's it! Databricks automatically handles:
# βœ… surrogate_key generation
# βœ… effective_start and effective_end timestamps
# βœ… is_current flag
# βœ… Deduplication of source data
# βœ… Ordering by sequence_by column
# βœ… Closing old rows and inserting new rows

Interview tip: Mention BOTH approaches. "I can implement SCD Type 2 manually using the merge_key trick for full control, but in production I prefer apply_changes in Lakeflow because it's simpler and handles edge cases automatically."

SECTION 3: CHANGE DATA CAPTURE (CDC) (1 hour)

Answer First: CDC (Change Data Capture) means capturing every change (insert, update, delete) that happens in a source database and streaming those changes to your data platform.

Memory Map: CDC? Design a pipeline: Oracle β†’ Kafka β†’ Delta Lake -> Oracle log sequence orders row changes -> Kafka preserves partitioned mutation envelopes -> streaming consumer deduplicates and merges keys -> source-target counts prove CDC completeness [DB_02_ETL_Pipelines_Databricks.md:346].

Q7: What is CDC? Design a pipeline: Oracle β†’ Kafka β†’ Delta Lake

Simple Explanation: CDC (Change Data Capture) means capturing every change (insert, update, delete) that happens in a source database and streaming those changes to your data platform.

Why do we need it? The old way: Run a batch job every night that copies the ENTIRE table from Oracle to your data lake (full extract). This is slow, expensive, and data is always 12-24 hours stale.

The CDC way: Capture only the CHANGES as they happen in real-time. A new booking? Stream it. A cancellation? Stream it. Data arrives in minutes, not hours.

Real-world analogy: Instead of photocopying the entire phone book every day to check for changes, you set up a notification: "Tell me whenever someone changes their number." That's CDC.

How does Debezium work? (the most popular CDC tool) Debezium reads the Oracle redo logs (database's internal change diary). It doesn't query the database β€” so there's ZERO impact on Oracle performance. It produces a Kafka message for every insert, update, or delete.

πŸ“ Architecture Diagram
End-to-end flow for a travel platform:

ORACLE (Legacy System)       KAFKA                    DATABRICKS
═══════════════════         ═════                    ═══════════
Bookings table          β†’   Debezium reads      β†’   BRONZE: Raw CDC events
  INSERT new booking        Oracle redo logs         (append every event as-is)
  UPDATE booking status     and sends to Kafka            β”‚
  DELETE cancelled          topics                        β–Ό
                                                    SILVER: Apply changes
Passengers table        β†’   One Kafka topic     β†’     MERGE (for facts)
  UPDATE address            per source table           apply_changes (for dims)
  UPDATE loyalty tier                                      β”‚
                                                          β–Ό
Flights table           β†’                        β†’   GOLD: Business metrics
  UPDATE schedule                                     Daily revenue, route stats
  UPDATE gate assignment                              ML feature tables

Architecture decisions explained:

πŸ—‚οΈ1. SOURCE: Debezium Oracle Connector
Reads Oracle redo logs (NOT SQL queries β†’ zero DB performance impact)
Captures every INSERT, UPDATE, DELETE with before/after values
Each message looks like:
β”‚ {
β”‚ "op": "u", // Operation: c=create, u=update, d=delete
β”‚ "before": {"id": 1, "status": "CONFIRMED"}, // Old values
β”‚ "after": {"id": 1, "status": "CHECKED_IN"}, // New values
β”‚ "ts_ms": 1711234567890 // When the change happened in Oracle
β”‚ }
No code changes needed in Oracle β€” just configure the connector
2. KAFKA (Azure Event Hubs β€” managed Kafka service)
One topic per source table (bookings_cdc, passengers_cdc, flights_cdc)
Messages partitioned by primary key β†’ ensures ordering per record
Retention: 7 days (if pipeline fails, you can replay from Kafka)
Why Kafka? Decouples source from destination. Oracle doesn't need to know about Databricks.
3. BRONZE LAYER: Store raw CDC events as-is
Use Auto Loader or Kafka connector to read from Event Hubs
Append every event β€” never update or delete in Bronze
Add metadata: _ingested_at, _kafka_offset, _kafka_partition
Keep the full CDC structure: {before, after, op, ts_ms}
4. SILVER LAYER: Apply the changes to create clean tables
Fact tables (bookings): Use MERGE to apply inserts/updates/deletes
Dimension tables (passengers): Use apply_changes for SCD Type 2
Use ts_ms to handle out-of-order events (latest timestamp wins)
5. GOLD LAYER: Business aggregations
Daily booking revenue by route
Airline performance metrics
Passenger 360 view for ML

Interview tip: This is a VERY likely question for a travel platform since they're migrating from Oracle. Be ready to draw this architecture and explain each component.

Delta integration summary β€” Q8: What is Change Data Feed (CDF)? How is it different from CDC?: Databricks pipelines can consume Delta CDF for incremental downstream work; feature enablement, change metadata, retention, and read APIs remain Delta-owned details. Open the canonical Delta answer.

SECTION 4: AUTO LOADER (45 min)

Q9: What is Auto Loader?

Simple Explanation: Auto Loader is a Databricks feature that automatically detects and loads new files from cloud storage (like ADLS Gen2). You point it at a folder, and whenever new files appear, Auto Loader picks them up and loads them into a Delta table.

Why do we need it? Without Auto Loader: You write custom code to check "are there new files?", track which files were already processed, handle failures, etc. Lots of boilerplate. With Auto Loader: Just point to a folder and say "load new files." It handles everything automatically β€” file tracking, exactly-once processing, schema detection.

Real-world analogy: Auto Loader is like a mailroom worker. New mail (files) arrives in the mailbox (ADLS folder). The worker automatically picks it up, stamps it as "processed", and delivers it to the right desk (Delta table). You don't need to keep checking the mailbox yourself.

Two modes:

ModeHow It Finds New FilesBest For
Directory Listing (default)Lists all files in the folder, compares with checkpoint to find new onesSimple setups, <10K files/day
File NotificationAzure Event Grid sends a notification when a new file arrivesHigh volume (>10K files/day), lower latency
🧠 Memory Map
# Auto Loader example: Load booking JSON files from ADLS Gen2 into Bronze Delta table
# READING: Auto Loader detects and reads new files
df = spark.readStream \
.format("cloudFiles") \ # "cloudFiles" = Auto Loader
.option("cloudFiles.format", "json") \ # Source files are JSON format
.option("cloudFiles.schemaLocation",
"/checkpoints/bookings/schema") \ # Where to store inferred schema
.option("cloudFiles.inferColumnTypes",
"true") \ # Auto-detect column types (int, string, etc.)
.load("abfss://raw@storage.dfs.core.windows.net/bookings/")
# ↑ The ADLS Gen2 folder to watch for new files
# Auto Loader will ONLY process files it hasn't seen before
# WRITING: Write new data to Bronze Delta table
df.writeStream \
.format("delta") \ # Write as Delta format
.option("checkpointLocation",
"/checkpoints/bookings/") \ # Checkpoint = "bookmark" tracking progress
.option("mergeSchema", "true") \ # If schema changes, merge new columns in
.trigger(availableNow=True) \ # Process all available files RIGHT NOW, then stop
.toTable("bronze.bookings") # Target Delta table name
# trigger options:
# - availableNow=True→Process all new files, then stop (for scheduled batch jobs)
# - processingTime="5 min"β†’Check for new files every 5 minutes (for near-real-time)
# - once=True→Process once and stop (one-time backfill)

Interview tip: Auto Loader is the recommended way to ingest files in Databricks. If asked "How would you ingest files?", always mention Auto Loader first.

Q10: How does Auto Loader handle schema changes?

Simple Explanation: In real life, source systems change their data format. An airline might add a new field "baggage_count" to their booking JSON. Auto Loader can handle this automatically β€” you choose how:

ModeWhat Happens When New Columns AppearWhen to Use
addNewColumns (default)New columns are added to the Delta table automaticallyDev/testing β€” flexible
rescueNew columns are stored in a special _rescued_data column as JSONProduction β€” safest
failOnNewColumnsPipeline STOPS with an error β€” you must manually handle itStrict schema control
noneNew columns are silently ignoredWhen you don't care about new fields
# RECOMMENDED FOR PRODUCTION: rescue mode
df = spark.readStream.format("cloudFiles") \
.option("cloudFiles.format", "json") \
.option("cloudFiles.schemaEvolutionMode", "rescue") \
# ↑ "rescue" = if a file has unexpected columns, put them in _rescued_data
# This way, your pipeline NEVER breaks because of schema changes
# You can inspect _rescued_data later and decide what to do
.option("cloudFiles.schemaLocation", "/checkpoints/schema/") \
.load(path)
# After loading, check _rescued_data for unexpected columns:
# df.filter("_rescued_data IS NOT NULL").show()
# If you find valid new columns, update your schema to include them

Real-world scenario: Airline X starts sending "meal_preference" in their booking JSON. Without rescue mode, your pipeline might break. With rescue mode, "meal_preference" goes into _rescued_data, pipeline continues running, and you handle it when ready.

Q11: Auto Loader vs COPY INTO β€” when to use which?

Simple Explanation: Both load files into Delta tables. The difference is: Auto Loader is a streaming approach (continuously watches for new files), while COPY INTO is a batch SQL command (you run it manually or on a schedule).

AspectAuto LoaderCOPY INTO
TypeStreaming (continuous)Batch (SQL command, run manually)
How it tracks filesAutomatic checkpoint (never reprocesses)Tracks via file metadata
Schema evolutionFull support (rescue mode, etc.)Limited
ScaleHandles millions of filesHandles thousands of files
Exactly-onceYes (checkpoint-based)Yes (idempotent β€” safe to rerun)
When to useRegular/frequent ingestionOne-time loads, ad-hoc imports
sql
-- COPY INTO example (simple one-off load):
COPY INTO bronze.bookings
FROM 'abfss://raw@storage.dfs.core.windows.net/bookings/'
FILEFORMAT = JSON
-- This loads all files from the folder. If you run it again, it skips already-loaded files.

Rule of thumb: Always prefer Auto Loader. Use COPY INTO only for simple, one-time, ad-hoc loads.

SECTION 5: LAKEFLOW DECLARATIVE PIPELINES (formerly DLT) (1 hour)

Answer First: Lakeflow Declarative Pipelines is Databricks' managed ETL framework. Instead of writing complex pipeline code (read data, clean it, write it, handle errors, manage retries...), you just DECLARE what your tables should look like, and Databricks handles the rest.

Memory Map: Lakeflow? (formerly called DLT / Delta Live Tables) -> declarative datasets form a managed dependency graph -> expectations attach quality actions -> engine maintains incremental state and retries -> event log records lineage and failures [DB_02_ETL_Pipelines_Databricks.md:589].

Q12: What is Lakeflow? (formerly called DLT / Delta Live Tables)

Simple Explanation: Lakeflow Declarative Pipelines is Databricks' managed ETL framework. Instead of writing complex pipeline code (read data, clean it, write it, handle errors, manage retries...), you just DECLARE what your tables should look like, and Databricks handles the rest.

Why "declarative"? Because you declare WHAT you want, not HOW to do it:

  • Regular code: "Read from this source, filter out nulls, join with this table, write to this target, handle errors..."
  • Declarative: "This table should have no nulls and should come from that source." Databricks figures out the HOW.

Name history (important for interviews!):

  • Before June 2025: Called "Delta Live Tables (DLT)"
  • After June 2025: Renamed to "Lakeflow Declarative Pipelines"
  • In Apache Spark: The core technology was contributed as "Spark Declarative Pipelines"
  • Use the new name in interviews to show you're up to date!

What Lakeflow gives you (that regular code doesn't):

  • Built-in data quality checks (expectations)
  • Automatic lineage tracking (see which table depends on which)
  • Managed infrastructure (serverless available β€” no cluster management)
  • Automatic recovery from failures
  • Easy SCD Type 2 with apply_changes (see Q6 above)

Two table types in Lakeflow:

TypeWhat It DoesWhen to Use
Streaming TableProcesses ONLY new data (incremental)Append-heavy data, CDC events, real-time
Materialized ViewRecomputes the ENTIRE result each timeAggregations, complex joins, Gold layer summaries

Real-world analogy:

  • Streaming Table = A conveyor belt that processes only new items
  • Materialized View = A report that's regenerated from scratch each time you ask for it

Q13: What are Data Quality Expectations? (3 levels)

Simple Explanation: Expectations are data quality rules you attach to your Lakeflow tables. They check every row as it flows through your pipeline. Think of them as quality inspectors on an assembly line.

There are 3 levels depending on what should happen when bad data is found:

python β€” editable
import dlt  # Lakeflow library

@dlt.table  # This decorator says "this function defines a Lakeflow table"

# Level 1: EXPECT β€” log the bad row but KEEP it (monitoring only)
@dlt.expect("valid_booking_id", "booking_id IS NOT NULL")
# "If booking_id is null, log a warning but still keep the row"
# Use when: You want to MONITOR data quality without blocking the pipeline

# Level 2: EXPECT_OR_DROP β€” silently DROP bad rows
@dlt.expect_or_drop("valid_amount", "fare_amount > 0")
# "If fare_amount is 0 or negative, drop the row (don't include it in the table)"
# Use when: Bad rows are useless garbage β€” just filter them out

# Level 3: EXPECT_OR_FAIL β€” STOP the entire pipeline
@dlt.expect_or_fail("valid_currency", "currency IN ('USD','EUR','GBP','INR')")
# "If currency is not one of these, STOP everything. Something is very wrong."
# Use when: This is a CRITICAL data integrity issue β€” pipeline must not continue

def silver_bookings():
    return spark.readStream.table("bronze.bookings")

Summary:

ExpectationBad Data?PipelineReal Use
@dlt.expectKept + loggedContinues"Track how many bad rows we get" (monitoring)
@dlt.expect_or_dropDropped silentlyContinues"Remove known junk data" (filtering)
@dlt.expect_or_failN/ASTOPS"Something critical is broken β€” investigate!"

New in 2026: Expectations are now stored in Unity Catalog β€” version-controlled, auditable, and shareable across pipelines. You can define an expectation once and reuse it in multiple pipelines.

Interview tip: "In our travel-platform pipeline, we use expect at Bronze to monitor raw data quality, expect_or_drop at Silver to filter invalid bookings, and expect_or_fail at Gold for critical business rules like 'departure date must be after booking date.'"

Answer First: Streaming Table = Processes only NEW data. Like a factory assembly line β€” it only works on items that just arrived. It remembers what it already processed and never redoes old work.

Memory Map: the difference between Materialized View and Streaming Table -> materialized view refreshes stored query results -> streaming table incrementally consumes arriving records -> latency and dependency model select ownership -> refresh history validates serving freshness [DB_02_ETL_Pipelines_Databricks.md:668].

Q14: What is the difference between Materialized View and Streaming Table?

Simple Explanation:

Streaming Table = Processes only NEW data. Like a factory assembly line β€” it only works on items that just arrived. It remembers what it already processed and never redoes old work.

Materialized View = Recomputes EVERYTHING from scratch. Like a daily summary report β€” it reads all the data and produces a fresh result each time. Slower, but always accurate.

python β€” editable
import dlt

# STREAMING TABLE β€” incremental processing (only new data)
@dlt.table
def silver_bookings():
    return spark.readStream.table("LIVE.bronze_bookings_raw")
    # readStream = streaming = only processes NEW rows since last run
    # Use for: Continuously adding new bookings as they arrive

# MATERIALIZED VIEW β€” full recompute (all data, every time)
@dlt.table
def gold_daily_revenue():
    return spark.sql("""
        SELECT
            booking_date,
            departure_airport,
            arrival_airport,
            SUM(fare_amount) AS total_revenue,     -- Sum all fares
            COUNT(*) AS booking_count               -- Count all bookings
        FROM LIVE.silver_bookings                   -- Read from Silver layer
        GROUP BY booking_date, departure_airport, arrival_airport
    """)
    # No readStream = batch = recomputes the entire aggregation each time
    # Use for: Gold aggregations that need to reflect all data including updates
AspectStreaming TableMaterialized View
ProcessingOnly new/unprocessed dataEverything from scratch
SpeedFast (less work each run)Slower (reads all data)
Source requirementMust be a streaming sourceAny table or view
Best forBronze→Silver (append-heavy, CDC)Gold layer (aggregations, joins)
Handles updates?Appends only (unless using apply_changes)Yes (recomputes all)

Answer First: Daily revenue report β†’ TRIGGERED (run once a day at midnight)

Memory Map: TRIGGERED vs CONTINUOUS pipeline mode -> bounded runs drain pending updates and release resources -> persistent execution waits for new arrivals -> freshness requirement trades idle capacity against startup delay -> event log confirms achieved latency [DB_02_ETL_Pipelines_Databricks.md:713].

Q15: What is TRIGGERED vs CONTINUOUS pipeline mode?

Simple Explanation: When you create a Lakeflow pipeline, you choose how it runs:

ModeWhat It DoesAnalogyWhen to Use
TRIGGEREDProcess all available data, then STOPLike a batch job β€” run, finish, doneScheduled runs (daily at 6am, hourly)
CONTINUOUSRun FOREVER, process data as it arrivesLike a 24/7 factory lineReal-time use cases (live dashboards)

Example:

  • Daily revenue report β†’ TRIGGERED (run once a day at midnight)
  • Live flight status board β†’ CONTINUOUS (update within seconds)

SECTION 6: SCENARIO-BASED PIPELINE DESIGN (1 hour)

Answer First: This is a "system design" interview question. They want to see HOW you'd build an end-to-end pipeline. Think about: sources, ingestion, layers, compute, governance, monitoring.

Memory Map: a booking pipeline for a travel platform (10 billion events/day) -> booking events enter partitioned Kafka topics -> bronze retains replayable reservations -> silver resolves traveler and itinerary changes -> gold latency and volume checks protect serving [DB_02_ETL_Pipelines_Databricks.md:731].

Q16: Design a booking pipeline for a travel platform (10 billion events/day)

Simple Explanation: This is a "system design" interview question. They want to see HOW you'd build an end-to-end pipeline. Think about: sources, ingestion, layers, compute, governance, monitoring.

πŸ“ Architecture Diagram
ARCHITECTURE OVERVIEW:
═══════════════════════════════════════════════════════════════

SOURCES (200+ airlines send data in different ways):
β”œβ”€β”€ REST APIs β†’ push booking events
β”œβ”€β”€ SFTP files β†’ daily CSV/JSON file drops
└── Kafka streams β†’ real-time event streams

INGESTION:
β”œβ”€β”€ Files from SFTP β†’ Auto Loader (watches ADLS folder for new files)
└── Kafka streams β†’ Kafka connector (reads from Azure Event Hubs)

BRONZE LAYER (raw data, as-is):
β”œβ”€β”€ What: Store every event exactly as received β€” never modify
β”œβ”€β”€ How: Streaming tables via Lakeflow Pipelines
β”œβ”€β”€ Schema: raw JSON with extra metadata columns:
β”‚   - _ingested_at (when we received it)
β”‚   - _source_airline (which airline sent it)
β”‚   - _batch_id (which batch it belongs to)
β”œβ”€β”€ Clustering: BY (ingestion_date, source_airline)
β”œβ”€β”€ Retention: 90 days (for debugging and replay)
└── Quality: @dlt.expect_or_drop("booking_id IS NOT NULL")

SILVER LAYER (cleaned, validated):
β”œβ”€β”€ What: Deduplicated, validated, business keys applied
β”œβ”€β”€ Deduplication: ROW_NUMBER by booking_id + updated_at (keep latest)
β”œβ”€β”€ Quality expectations:
β”‚   - expect: passenger_count > 0 (monitor)
β”‚   - expect_or_drop: booking_id IS NOT NULL (filter junk)
β”‚   - expect_or_fail: departure_date > booking_date (critical rule)
β”œβ”€β”€ Dimensions: SCD Type 2 for dim_passenger (using apply_changes)
β”œβ”€β”€ Facts: SCD Type 1 for fact_bookings (using MERGE β€” overwrite)
└── Clustering: BY (booking_id)

GOLD LAYER (business-ready):
β”œβ”€β”€ daily_booking_revenue β†’ Materialized View (aggregation by route)
β”œβ”€β”€ airline_performance_metrics β†’ Materialized View
β”œβ”€β”€ passenger_360 β†’ Wide denormalized table for ML models
└── Clustering: BY (booking_date, departure_airport)

COMPUTE (which machines run what):
β”œβ”€β”€ Ingestion: Serverless streaming (auto-scales with data volume)
β”œβ”€β”€ Silver MERGE: Job cluster + Photon (heavy transformations need power)
β”œβ”€β”€ Gold queries: Serverless SQL Warehouse (BI tools like Power BI connect here)
└── ML: GPU cluster for fare prediction models

GOVERNANCE (who can access what):
β”œβ”€β”€ Unity Catalog: travel_catalog.bookings.* (3-level namespace)
β”œβ”€β”€ PII masking: passenger email and phone masked for non-authorized users
β”œβ”€β”€ Row-level security: each airline partner sees only their own bookings
└── GDPR: deletion pipeline for "right to be forgotten" requests

MONITORING (is everything healthy?):
β”œβ”€β”€ Lakeflow expectations dashboard (% of rows passing quality checks)
β”œβ”€β”€ Row count anomaly detection (new Feb 2026 feature)
β”œβ”€β”€ SLA tracking: Bronze < 5 min latency, Silver < 30 min, Gold < 1 hour
└── PagerDuty alerts for pipeline failures or expectation breaches

Interview tip: Draw this on a whiteboard. Start from sources on the left, flow to Gold on the right. Mention compute, governance, and monitoring separately β€” it shows you think about production systems, not just data transformations.

Delta integration summary β€” Q17: Scenario β€” Our MERGE on booking fact table takes 3 hours. How do you fix it?: Databricks jobs invoke Delta MERGE, but scan pruning, source deduplication, file layout, and operation-metric diagnosis belong to the canonical Delta optimization answer. Open the canonical Delta answer.

Answer First: A legacy company currently uses old Sqoop jobs that do a FULL COPY of Oracle tables every night (slow, 12-24 hours stale). The goal: switch to real-time CDC using Debezium β†’ Kafka β†’ Delta Lake (< 5 minute latency).

Memory Map: Scenario β€” Migrate Oracle CDC to Delta Lake for a travel platform -> Oracle SCN anchors migration ordering -> historical snapshot establishes initial Delta state -> streamed mutations catch up without gaps -> cutover reconciliation proves parity [DB_02_ETL_Pipelines_Databricks.md:847].

Q18: Scenario β€” Migrate Oracle CDC to Delta Lake for a travel platform

Simple Explanation: A legacy company currently uses old Sqoop jobs that do a FULL COPY of Oracle tables every night (slow, 12-24 hours stale). The goal: switch to real-time CDC using Debezium β†’ Kafka β†’ Delta Lake (< 5 minute latency).

πŸ—‚οΈCURRENT STATE (what we have now):
Oracle DB with 500 tables
Sqoop batch jobs run every night (full table copy β†’ ADLS β†’ Hive tables)
Problem 1: Data is 12-24 hours stale (not real-time)
Problem 2: Full copies are expensive (copying 500 tables every night)
Problem 3: No ACID, no governance, no quality checks
TARGET STATE (what we want):
Real-time CDC β†’ < 5 minute latency
Delta Lake on ADLS Gen2 β†’ ACID transactions
Unity Catalog β†’ governance and PII protection
Lakeflow β†’ automated quality checks
MIGRATION PLAN:
Phase 1 (Weeks 1-4): SET UP FOUNDATION
Deploy Debezium Oracle connector β†’ Azure Event Hubs (managed Kafka)
β”‚ Debezium reads Oracle redo logs β†’ produces change events to Kafka topics
Set up Databricks workspace with Unity Catalog
β”‚ Create catalogs: travel_prod, travel_dev, travel_staging
Bronze layer: Auto Loader reads from Event Hubs
β”‚ Store raw CDC events as-is (append-only)
Test with 1 non-critical table first (e.g., flight_schedules)
Phase 2 (Weeks 5-8): MIGRATE TOP 20 TABLES
Migrate the 20 most important tables (by business value)
β”‚ bookings, passengers, flights, fares, routes...
Silver: MERGE for fact tables, apply_changes (SCD2) for dimensions
Validate DAILY: count rows in Oracle vs Delta (must match!)
Run BOTH old Sqoop AND new CDC in parallel for 2 weeks (safety net)
Phase 3 (Weeks 9-12): MIGRATE REMAINING 480 TABLES
Migrate all remaining tables using the same pattern
Decommission Sqoop jobs one by one (after validation)
Build Gold layer: recreate existing reports and dashboards
Connect BI tools (Power BI) to Databricks SQL Warehouse
Phase 4 (Weeks 13-16): OPTIMIZE
Enable Predictive Optimization on all managed tables
Set up Lakeflow Pipelines for critical data paths
Build GDPR deletion pipeline for passenger PII
Cost optimization: right-size clusters, use Spot VMs

Answer First: Data quality = making sure your data is correct, complete, and timely at every layer. Without quality checks, bad data silently flows through your pipeline and shows up in business reports β€” very dangerous.

Memory Map: you design a Data Quality Framework -> domain rules become executable expectations -> severity routes warnings, quarantine, or failure -> metrics aggregate violations by dataset -> ownership workflow closes recurring defects [DB_02_ETL_Pipelines_Databricks.md:899].

Q19: How do you design a Data Quality Framework?

Simple Explanation: Data quality = making sure your data is correct, complete, and timely at every layer. Without quality checks, bad data silently flows through your pipeline and shows up in business reports β€” very dangerous.

# BRONZE LAYER: Basic checks β€” "did we receive the minimum required fields?"
@dlt.expect_or_drop("has_required_fields",
"booking_id IS NOT NULL AND flight_id IS NOT NULL")
# Drop rows that don't even have basic required fields β€” they're garbage
# SILVER LAYER: Business rule checks β€” "does this data make sense?"
@dlt.expect("valid_dates", "departure_date >= booking_date")
# A flight can't depart BEFORE it was booked β€” flag if it does (but keep the row)
@dlt.expect("valid_fare", "fare_amount > 0 AND fare_amount < 100000")
# Fare should be positive and reasonable (not $0 or $1 million)
@dlt.expect_or_fail("valid_airport",
"departure_airport IN (SELECT iata_code FROM ref_airports)")
# Airport code must exist in our reference table β€” if not, something is VERY wrong, STOP pipeline
# GOLD LAYER: Completeness checks
@dlt.expect("complete_data", "passenger_name IS NOT NULL")
# Gold layer should have complete data β€” flag if passenger name is missing
# MONITORING (NEW β€” February 2026 feature):
# Data Quality Monitoring Anomaly Detection
# Databricks automatically detects unexpected changes in:
# - Row counts (suddenly 50% fewer rows than usual)
# - Null rates (null % jumped from 1% to 20%)
# - Value distributions (a column that's usually 99% "USD" is now 50% "USD")
# No configuration needed β€” it learns normal patterns and alerts on anomalies

Interview tip: Show you think about quality at EVERY layer, not just one. "We put expect_or_drop at Bronze for garbage filtering, expect at Silver for business rules monitoring, and expect_or_fail at Gold for critical data integrity."

QUICK REVISION CHECKLIST β€” DAY 2

Test yourself β€” can you answer each in 2-3 minutes?

  • What is Medallion Architecture? Explain Bronze/Silver/Gold with Examples. (Q1)
  • When would you NOT follow Medallion? (Q2)
  • What is SCD? Explain Types 0, 1, 2, 3 with examples. (Q4)
  • Can you write SCD Type 2 with the merge_key trick? Explain how the trick works. (Q5)
  • Can you write SCD Type 2 with apply_changes in Lakeflow? (Q6)
  • What is CDC? Design Oracle β†’ Kafka β†’ Delta Lake pipeline. (Q7)
  • What is CDF (Change Data Feed)? How is it different from CDC? (Q8)
  • What is Auto Loader? Explain the two modes. (Q9)
  • How does Auto Loader handle schema changes? What is rescue mode? (Q10)
  • Auto Loader vs COPY INTO β€” when to use each? (Q11)
  • What is Lakeflow? What was it called before? (Q12)
  • What are the 3 levels of expectations? Give examples. (Q13)
  • Streaming Table vs Materialized View β€” when to use each? (Q14)
  • Can you design a 10B events/day booking pipeline? (Q16)
  • How would you fix a 3-hour slow MERGE? (Q17)
  • Design an Oracle β†’ CDC β†’ Delta Lake migration plan. (Q18)

ETL Patterns β€” Quick Recall

πŸ—ΊοΈ Memory Map
How to use this file:
  • ⚑ = Must remember (95% chance of being asked)
  • πŸ”‘ = Key concept (core understanding needed)
  • ⚠️ = Common trap (interviewers love to test this)
  • 🧠 = Memory Map (mnemonic/acronym β€” memorize this!)
  • πŸ“ = One-liner (flash-card style β€” cover answer, test yourself)
Reading strategy: Read Memory Maps FIRST β†’ then Direct Questions β†’ then Mid-Level.

🧠 MASTER MEMORY MAP β€” Day 2

🧠 ETL PIPELINE DESIGN = "MACS-L"
ETL PIPELINE DESIGN"MACS-L"
MMedallion Architecture (Bronze β†’ Silver β†’ Gold)
AAuto Loader (streaming file ingestion)
CCDC (Change Data Capture β€” Oracle β†’ Kafka β†’ Delta)
SSCD Type 2 (Slowly Changing Dimensions β€” history tracking)
LLakeflow Declarative Pipelines (formerly DLT)
MEDALLION"BSG" (like the TV show Battlestar Galactica)
BBronze (raw, as-is, append-only)
SSilver (clean, deduplicated, validated)
GGold (aggregated, business-ready)
AUTO LOADER"DNS"
DDirectory listing mode (small folders, simple)
NNotification mode (huge folders, uses Azure Event Grid)
SSchema evolution (auto-detects new columns β†’ rescue)

SECTION 1: MEDALLION ARCHITECTURE

🧠 Memory Map: Medallion Layers

🧠 BRONZE = Raw ingredients from market (dirty, uncut)
Think: COOKING ANALOGY
BRONZERaw ingredients from market (dirty, uncut)
β†’ Append only, never update
β†’ Add metadata: _ingested_at, _source_file, _batch_id
β†’ Engineers read this for debugging
SILVERWashed, cut, measured ingredients
β†’ Deduplicated (MERGE/upsert)
β†’ Schema validated, nulls handled
β†’ PII tagged (Unity Catalog)
β†’ Engineers + Scientists read this
GOLDFinal dish served to customer
β†’ Aggregated (daily revenue, route stats)
β†’ Star schema for BI tools
β†’ Power BI, Tableau read this
KEY DESIGN DECISIONS per layer:
Bronze: partition by ingestion_date (when data arrived)
Silver: cluster by business_key (booking_id)
Gold: cluster by query pattern (airport, date)

⚑ MUST KNOW DIRECT QUESTIONS

Q1What is Medallion Architecture?

A data organization pattern with 3 layers: Bronze (raw), Silver (clean), Gold (business-ready). Data flows through layers with increasing quality.

Q2What goes into Bronze layer?

Raw data exactly as received β€” no transformations. Just append with metadata columns (_ingested_at, _source_file). Never update or delete in Bronze.

Q3What goes into Silver layer?

Cleaned, deduplicated, validated data. Apply MERGE for upserts, fix data types, remove nulls, apply business keys, tag PII columns.

Q4What goes into Gold layer?

Business-ready aggregations, KPIs, star schema. Optimized for fast queries by BI tools (Power BI, Tableau). Pre-computed metrics like daily revenue per route.

Q5Who reads each layer?

Bronze = data engineers (debugging). Silver = engineers + data scientists. Gold = business analysts + BI tools.

⚠️ Q6When would you skip a layer?

  • Skip Silver: Source data is already clean β†’ Bronze straight to Gold
  • Skip Gold: Real-time dashboard reads from Silver directly
  • Add layers: "Feature layer" between Silver and Gold for ML features

Q7What is late-arriving data?

Data that arrives after its actual event time. Example: A booking from March 1 arrives on March 5 (airline system was offline). Handle with MERGE on business key β€” it updates Silver even if it arrives late.

πŸ”‘ MID-LEVEL QUESTIONS

Q8How do you handle late-arriving data in each layer?

LayerStrategy
BronzeJust append β€” it's append-only anyway. Add _ingested_at to track when it arrived
SilverMERGE on business key β€” upserts handle late data naturally
GoldRecompute affected aggregation windows (reprocess March 1 aggregations)

Q9What metadata should Bronze have?

5 essential columns:

  1. _ingested_at β€” when data was loaded (TIMESTAMP)
  2. _source_file β€” which file it came from (STRING)
  3. _batch_id β€” which batch run loaded it (STRING)
  4. _source_system β€” which system sent it (STRING)
  5. _raw_payload β€” original data if JSON (for debugging)

Q10Medallion vs Data Mesh β€” what's the difference?

  • Medallion = centralized pipeline (one team owns Bronzeβ†’Silverβ†’Gold)
  • Data Mesh = decentralized (each domain team owns their own Bronzeβ†’Silverβ†’Gold)
  • Can combine both: each domain team follows Medallion within their domain

SECTION 2: SCD (SLOWLY CHANGING DIMENSIONS)

🧠 Memory Map: SCD Types

🧠 SCD = "How do you handle dimension changes over time?"
SCD"How do you handle dimension changes over time?"
Think: A PASSENGER CHANGES THEIR ADDRESS
SCD Type 0 = "Never change"β†’Ignore the update (keep original forever)
SCD Type 1 = "Overwrite"β†’Replace old address with new (no history)
SCD Type 2 = "Add new row"β†’Keep old row + add new row (FULL history) ⭐
SCD Type 3 = "Add new column"β†’Add previous_address column (limited history)
Remember: "0-N-O-C" = Zero change, No history, Old+new rows, Column added
⚑ INTERVIEW STAR: SCD TYPE 2 = Most asked! Know the code!
SCD Type 2 columns:
booking_id→business key (never changes)
passenger_name→can change
address→can change
start_date→when this version became active
end_date→when this version was replaced (NULL = current)
is_current→TRUE for latest version, FALSE for old

⚑ MUST KNOW DIRECT QUESTIONS

Q11What is SCD?

Slowly Changing Dimension β€” how you handle changes to dimension tables over time. Example: a passenger changes their address β€” do you overwrite, keep history, or ignore?

Q12What is SCD Type 1?

Overwrite the old value with the new value. No history kept. Simple but you lose what the old value was.

⚑ Q13What is SCD Type 2?

Add a new row for each change. Keep the old row with end_date and is_current = false. Full history preserved. Most common in data warehousing.

Q14What is SCD Type 3?

Add a column for the previous value (e.g., current_address + previous_address). Limited history β€” only tracks the last change.

⚠️ Q15Why is SCD Type 2 the hardest to implement with MERGE?

Because for ONE changed record, you need TWO operations:

  1. UPDATE the old row (close it: set end_date, is_current = false)
  2. INSERT a new row (open it: start_date = today, is_current = true)
Standard MERGE can't do both for the same key β€” that's why we use the merge_key trick.

πŸ”‘ MID-LEVEL QUESTIONS

⚑ Q16Explain the merge_key trick for SCD Type 2 (MOST IMPORTANT)

Problem: MERGE matches on booking_id. For a changed record, you need to UPDATE the old row AND INSERT a new row. But MERGE only does one action per matched key. Solution: Create a merge_key column in the staged data:

  • Row to UPDATE: merge_key = booking_id (matches target β†’ WHEN MATCHED β†’ UPDATE)
  • Row to INSERT: merge_key = NULL (never matches β†’ WHEN NOT MATCHED β†’ INSERT)
sql
-- Step 1: Create staged changes with TWO rows per changed record
staged_changes AS (
  -- Row 1: Close the old record (merge_key = booking_id β†’ will MATCH)
  SELECT booking_id AS merge_key, booking_id, address,
         current_timestamp AS end_date, false AS is_current
  FROM changes WHERE change_type = 'U'
  UNION ALL
  -- Row 2: Insert new record (merge_key = NULL β†’ will NOT MATCH β†’ INSERT)
  SELECT NULL AS merge_key, booking_id, new_address,
         NULL AS end_date, true AS is_current
  FROM changes WHERE change_type = 'U'
)
-- Step 2: MERGE with merge_key
MERGE INTO dim_passenger USING staged_changes
ON dim_passenger.booking_id = staged_changes.merge_key
   AND dim_passenger.is_current = true
WHEN MATCHED THEN UPDATE SET end_date = ..., is_current = false
WHEN NOT MATCHED THEN INSERT (...)

Q17What is the EASY way to do SCD Type 2?

Use Lakeflow apply_changes β€” it handles SCD Type 2 automatically:

python β€” editable
dlt.apply_changes(
  target="dim_passenger",       # Target table
  source="raw_passengers",      # Source stream
  keys=["passenger_id"],        # Business key
  sequence_by="updated_at",     # How to order changes
  stored_as_scd_type=2          # SCD Type 2 automatically!
)
No merge_key trick needed β€” Lakeflow handles the UPDATE + INSERT logic internally.

SECTION 3: CDC (CHANGE DATA CAPTURE)

🧠 Memory Map: CDC Pipeline

🧠 CDC = "Capture every change from source database"
CDC"Capture every change from source database"
CDC PIPELINE
Oracle DB→Debezium → Kafka → Auto Loader → Delta Lake
Oracle DB: Source (legacy booking system)
Debezium: Reads Oracle's redo logs (no impact on DB performance)
Kafka: Message bus (buffers + distributes changes)
Auto Loader: Reads from Kafka landing zone in ADLS
Delta Lake: Final destination (Bronze β†’ Silver β†’ Gold)
Remember: "O-D-K-A-D" = Oracle→Debezium → Kafka → AutoLoader → Delta
CDC Message types:
"c" = CREATE (new booking)
"u" = UPDATE (booking changed)
"d" = DELETE (booking cancelled)
"r" = READ (initial snapshot/backfill)
Remember: "CUDR" (sounds like "could-er")

⚑ MUST KNOW DIRECT QUESTIONS

Q18What is CDC?

Change Data Capture β€” captures every INSERT, UPDATE, DELETE from a source database and sends it downstream in real-time or near-real-time.

Q19What is Debezium?

Open-source CDC connector that reads database transaction logs (Oracle redo logs, MySQL binlog) and streams changes to Kafka. Zero impact on source database performance.

Q20Why use Debezium instead of direct queries?

  • Direct queries = poll the database β†’ high load on source, miss deletes, miss intermediate changes
  • Debezium = reads transaction logs β†’ zero load on source, captures ALL changes including deletes

Delta integration summary β€” ⚠️ Q21: What is CDF (Change Data Feed)? How is it different from CDC?: Databricks pipelines can consume Delta CDF for incremental downstream work; feature enablement, change metadata, retention, and read APIs remain Delta-owned details. Open the canonical Delta answer.

Delta integration summary β€” πŸ“ Q22: What columns does CDF add?: Databricks pipelines can consume Delta CDF for incremental downstream work; feature enablement, change metadata, retention, and read APIs remain Delta-owned details. Open the canonical Delta answer.

πŸ”‘ MID-LEVEL QUESTIONS

Q23Design a CDC pipeline (Oracle β†’ Delta Lake)

Oracle (bookings DB)
↓ Debezium reads redo logs
Kafka (topic: bookings.changes)
↓ Kafka Connect writes JSON to ADLS
ADLS Gen2 (/raw/cdc/bookings/)
↓ Auto Loader (cloudFiles) reads new files
Bronze Delta Table (raw CDC events β€” all c/u/d records)
↓ MERGE on booking_id + apply CDC logic
Silver Delta Table (current state of all bookings)
↓ Aggregate
Gold Delta Table (daily booking stats per route)

Q24How do you handle CDC deletes in Silver?

Two approaches:

  1. Soft delete: Set is_deleted = true, keep the row (for audit trail)
  2. Hard delete: Actually delete from Silver (for GDPR compliance)
sql
MERGE INTO silver_bookings t USING bronze_cdc s
ON t.booking_id = s.booking_id
WHEN MATCHED AND s.op = 'd' THEN DELETE  -- Hard delete
WHEN MATCHED AND s.op = 'u' THEN UPDATE SET ...
WHEN NOT MATCHED AND s.op = 'c' THEN INSERT ...

Q25What if CDC events arrive out of order?

Use a sequence column (like updated_at timestamp or lsn β€” log sequence number). During MERGE, only apply the change if the incoming record is NEWER:

sql
WHEN MATCHED AND s.updated_at > t.updated_at THEN UPDATE SET ...

SECTION 4: AUTO LOADER

🧠 Memory Map: Auto Loader

πŸ“ Architecture Diagram
AUTO LOADER = "Automatically ingest new files as they arrive"

cloudFiles = the Spark source name for Auto Loader

TWO MODES:
    Directory Listing β†’ Scans folder for new files (simple, small folders)
    File Notification β†’ Azure Event Grid notifies when new file arrives (scalable, huge folders)

    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚ < 10,000 files/day β†’ Directory Listing      β”‚
    β”‚ > 10,000 files/day β†’ File Notification      β”‚
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

SCHEMA EVOLUTION:
    New column appears β†’ Auto Loader adds it automatically
    Unexpected data β†’ Goes to _rescued_data column (not lost!)

Remember: "Auto Loader = cloudFiles + Directory or Notification + Schema rescue"

AUTO LOADER vs COPY INTO:
    Auto Loader = streaming (always on, processes new files automatically)
    COPY INTO   = batch (run manually, idempotent β€” safe to re-run)

⚑ MUST KNOW DIRECT QUESTIONS

Q26What is Auto Loader?

A Databricks feature that automatically ingests new files from cloud storage (ADLS Gen2) as they arrive. Uses cloudFiles as the Spark source format.

Q27What are the two modes of Auto Loader?

  1. Directory listing β€” scans folder periodically (good for <10K files/day)
  2. File notification β€” Azure Event Grid sends notifications (good for >10K files/day, more efficient)

Q28What is _rescued_data?

A special column where Auto Loader puts data that doesn't match the expected schema β€” instead of failing, unexpected fields are saved in this column for debugging.

Q29How does Auto Loader handle schema evolution?

Set cloudFiles.schemaEvolutionMode = "addNewColumns" β€” when source adds new columns, Auto Loader auto-adds them to the target table. Pipeline restarts to pick up the new schema.

⚠️ Q30Auto Loader vs COPY INTO β€” when to use which?
πŸ’‘ Interview Tip
Auto LoaderCOPY INTO
TypeStreaming (always on)Batch (run manually)
ScalabilityMillions of filesThousands of files
Schema evolutionAuto-detectsNo auto-detection
File trackingCheckpoint (remembers processed files)Idempotent (safe to re-run)
Use whenContinuous ingestionOne-time or scheduled loads

SECTION 5: LAKEFLOW DECLARATIVE PIPELINES (formerly DLT)

🧠 Memory Map: Lakeflow

πŸ“ Architecture Diagram
LAKEFLOW = "Declarative ETL β€” you say WHAT, it does HOW"

Old name: DLT (Delta Live Tables) β†’ renamed to "Lakeflow Declarative Pipelines" (2025)

TWO TABLE TYPES:
    Streaming Table = append-only, processes new data only (like a river)
    Materialized View = recomputed from scratch when data changes (like a snapshot)

    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚ Append-only data (logs, events) β†’ Streaming   β”‚
    β”‚ Aggregations, lookups β†’ Materialized View     β”‚
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

DATA QUALITY = EXPECTATIONS (3 levels):
    EXPECT        β†’ warn only (log bad rows, keep them)
    EXPECT OR DROP β†’ silently drop bad rows
    EXPECT OR FAIL β†’ stop pipeline on bad data

Remember: "WDF" = Warn, Drop, Fail
    W β€” expect (Warn)
    D β€” expect or DROP
    F β€” expect or FAIL

PIPELINE MODES:
    TRIGGERED   = run once, then stop (batch β€” for cost savings)
    CONTINUOUS  = always running (streaming β€” for low latency)

⚑ MUST KNOW DIRECT QUESTIONS

Q31What is Lakeflow (DLT)?

A declarative framework for building ETL pipelines. You define WHAT your tables should look like (SQL/Python), and Lakeflow figures out HOW to execute, manage dependencies, handle retries, and enforce data quality.

Q32What was DLT renamed to?

Lakeflow Declarative Pipelines (2025). The product is the same, just rebranded.

Q33What is a Streaming Table?

An append-only table that processes only NEW data (incremental). Like a river β€” new water flows through, old water is already downstream. Best for: logs, events, CDC streams.

Q34What is a Materialized View?

A table that is fully recomputed when underlying data changes. Like a fresh photo of your data. Best for: aggregations, star schema, Gold layer tables.

⚑ Q35What are the 3 levels of Expectations?

  1. EXPECT β€” warn only, bad rows kept (log for monitoring)
  2. EXPECT OR DROP β€” silently remove bad rows
  3. EXPECT OR FAIL β€” stop the entire pipeline
Example: "For booking amounts, we use EXPECT OR FAIL β€” a negative fare means corrupted data, stop immediately."

Q36What is apply_changes?

A Lakeflow function that automatically handles CDC/SCD logic. Give it a source stream, target table, keys, and SCD type β€” it does the MERGE internally.

Q37TRIGGERED vs CONTINUOUS mode?

  • TRIGGERED: Runs once, processes all available data, stops. (cheaper, for batch)
  • CONTINUOUS: Runs forever, processes data as it arrives. (expensive, for real-time)

πŸ”‘ MID-LEVEL QUESTIONS

Q38Streaming Table vs Materialized View β€” when to use which?

Streaming TableMaterialized View
ProcessingOnly NEW data (incremental)Recomputes ALL data
Good forHigh-volume append (CDC, logs)Aggregations, joins, Gold layer
Can UPDATE existing rows?No (append-only)Yes (full recompute)
CostLow (reads only new data)Higher (reprocesses everything)
Exampleraw_bookings Bronzedaily_revenue Gold

Q39How do Expectations work with Quarantine pattern?

Bad rows are not just dropped β€” they're sent to a quarantine table for investigation:

python β€” editable
@dlt.table
@dlt.expect_or_drop("valid_amount", "amount > 0")
def silver_bookings():
    return spark.readStream.table("bronze_bookings")

@dlt.table
def quarantine_bookings():  # Capture what was dropped
    return spark.readStream.table("bronze_bookings").filter("amount <= 0")

Q40How do you build a complete Lakeflow pipeline?

python β€” editable
import dlt

# Bronze: Auto Loader ingests raw files
@dlt.table
def bronze_bookings():
    return (spark.readStream.format("cloudFiles")
            .option("cloudFiles.format", "json")
            .load("/raw/bookings/"))

# Silver: Clean + validate
@dlt.table
@dlt.expect_or_drop("valid_id", "booking_id IS NOT NULL")
@dlt.expect_or_drop("valid_amount", "amount > 0")
def silver_bookings():
    return dlt.readStream("bronze_bookings")
             .select("booking_id", "passenger_id", "amount", "booking_date")

# Gold: Aggregate
@dlt.table
def gold_daily_revenue():
    return dlt.read("silver_bookings")
             .groupBy("booking_date")
             .agg(sum("amount").alias("total_revenue"))

SECTION 6: PIPELINE SCENARIOS

πŸ”‘ MID-LEVEL QUESTIONS

⚑ Q41How would you design a booking pipeline for a travel platform?
πŸ—ΊοΈ Memory Map
🧠 Memory Map
Sources:
Oracle (bookings) β†’ Debezium β†’ Kafka β†’ ADLS landing zone
Flight APIs (JSON) β†’ Azure Event Hubs β†’ ADLS landing zone
Loyalty system (CSV) β†’ SFTP β†’ ADLS landing zone
Bronze (Auto Loader):
cloudFiles reads from 3 landing zones→3 Bronze tables
Schema: raw + _ingested_at + _source_file
Silver (Lakeflow + MERGE):
Deduplicate on booking_id
Apply SCD Type 2 for passenger dimensions
Tag PII columns (Unity Catalog)
Quality: EXPECT OR FAIL for null booking_id
Gold (Materialized Views):
daily_bookings_by_route (Power BI dashboard)
passenger_360 (customer analytics)
revenue_per_airline (partner reporting)

Delta integration summary β€” ⚠️ Q42: MERGE takes 3 hours on fact table. How to fix?: Databricks jobs invoke Delta MERGE, but scan pruning, source deduplication, file layout, and operation-metric diagnosis belong to the canonical Delta optimization answer. Open the canonical Delta answer.

Q43How to build a Data Quality Framework?

4 layers:

  1. Schema validation (Bronze) β€” reject wrong formats at ingestion
  2. Row-level rules (Silver) β€” Lakeflow Expectations (NULL checks, range checks)
  3. Cross-table rules (Gold) β€” referential integrity (every booking has a valid passenger)
  4. Statistical monitoring β€” Lakehouse Monitoring detects drift (column mean shifted 20%)

🧠 FINAL REVISION β€” Day 2 Summary Card

πŸ“ Architecture Diagram
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    DAY 2: ETL PIPELINES                      β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                                             β”‚
β”‚  MEDALLION = Bronze(raw) β†’ Silver(clean) β†’ Gold(business)   β”‚
β”‚  Bronze: append-only + metadata (_ingested_at, _source)     β”‚
β”‚  Silver: MERGE + dedup + PII tagging                        β”‚
β”‚  Gold: aggregations for BI + ML                             β”‚
β”‚                                                             β”‚
β”‚  SCD Type 1 = Overwrite (no history)                        β”‚
β”‚  SCD Type 2 = Add new row (full history) ⭐                 β”‚
β”‚  merge_key trick: NULL key β†’ NOT MATCHED β†’ INSERT new row   β”‚
β”‚  Easy way: Lakeflow apply_changes(stored_as_scd_type=2)     β”‚
β”‚                                                             β”‚
β”‚  CDC = Oracle β†’ Debezium β†’ Kafka β†’ Auto Loader β†’ Delta      β”‚
β”‚  CDC ops: c=create, u=update, d=delete, r=read              β”‚
β”‚  CDF β‰  CDC! CDF = Delta's internal change tracking          β”‚
β”‚                                                             β”‚
β”‚  Auto Loader = cloudFiles source                            β”‚
β”‚  Two modes: Directory Listing (<10K) / Notification (>10K)  β”‚
β”‚  Schema evolution: addNewColumns + _rescued_data             β”‚
β”‚  Auto Loader (streaming) vs COPY INTO (batch)               β”‚
β”‚                                                             β”‚
β”‚  Lakeflow (was DLT):                                        β”‚
β”‚  Streaming Table = append-only, incremental                 β”‚
β”‚  Materialized View = full recompute                         β”‚
β”‚  Expectations: Warn / Drop / Fail ("WDF")                   β”‚
β”‚  apply_changes = auto SCD Type 2                            β”‚
β”‚  Modes: TRIGGERED (batch) vs CONTINUOUS (streaming)         β”‚
β”‚                                                             β”‚
β”‚  TOP 5 THINGS TO SAY IN INTERVIEW:                          β”‚
β”‚  1. "Medallion with Bronze append-only + Silver MERGE"      β”‚
β”‚  2. "SCD Type 2 with merge_key trick or apply_changes"      β”‚
β”‚  3. "CDC: Debezium reads redo logs, zero source impact"     β”‚
β”‚  4. "Auto Loader with notification mode for scale"          β”‚
β”‚  5. "Lakeflow Expectations: Warn/Drop/Fail for quality"     β”‚
β”‚                                                             β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
πŸ—ΊοΈ Memory Map
Study tip: Read this file TWICE:
  1. First pass (30 min): Read only 🧠 Memory Maps + ⚑ Direct Questions
  2. Second pass (30 min): Read πŸ”‘ Mid-Level Questions + ⚠️ Traps
  3. Before interview (15 min): Read ONLY the Final Revision Summary Card

TOPIC 2: ETL PIPELINES (Medallion Architecture, SCD Type 2, CDC, Auto Loader, DLT/Lakeflow)

L1 β€” Direct / Simple Questions

  1. What is the Medallion Architecture (Bronze/Silver/Gold)?

    A multi-layer data organization pattern. Bronze = raw ingestion (append-only, no transformations). Silver = cleansed and conformed (deduplication, schema enforcement, joins). Gold = business-level aggregations (ready for BI/reporting). Each layer improves data quality progressively.

2. What is Auto Loader in Databricks?

A streaming file ingestion tool that automatically detects and processes new files as they arrive in cloud storage (ADLS Gen2, S3). Uses cloudFiles format. Handles schema evolution, file tracking, and exactly-once processing.

3. What is the difference between Auto Loader and COPY INTO?

Auto Loader is streaming (continuous, processes files as they arrive, tracks state via RocksDB). COPY INTO is batch (one-time command, idempotent, tracks processed files in table metadata). Use Auto Loader for ongoing ingestion; COPY INTO for ad-hoc or small-scale loads.

4. What is Delta Live Tables (DLT)?

A declarative framework for building ETL pipelines. You define WHAT transformations to apply (using @dlt.table decorators) and DLT handles HOW to run them (orchestration, retries, checkpointing, data quality). Renamed to Lakeflow Declarative Pipelines in 2025.

5. What is Lakeflow and how does it relate to DLT?

Lakeflow is Databricks' unified data engineering platform. Lakeflow Declarative Pipelines (formerly DLT) is the ETL component. Lakeflow Connect handles ingestion from external sources. Lakeflow is the umbrella brand.

6. What is SCD Type 1 vs SCD Type 2?

SCD Type 1 = overwrite the old value (no history). SCD Type 2 = keep the old record (close it with an end_date), insert a new current record. Type 1 is simpler but loses history. Type 2 preserves full change history for auditing.

7. What is Change Data Capture (CDC)?

A pattern for identifying and capturing data changes (inserts, updates, deletes) from a source system. Methods: log-based (read database transaction logs β€” Debezium), query-based (compare snapshots), timestamp-based (filter by modified_date). Log-based is most efficient.

8. What is a streaming table vs a materialized view in DLT?

Streaming table: append-only, processes each record exactly once, used for incremental ingestion (Bronze/Silver). Materialized view: fully recomputed on each refresh, used for aggregations (Gold). Streaming tables track state; materialized views don't.

9. What are DLT expectations (data quality constraints)?

Declarative rules that check data quality during pipeline execution. Three actions on violation: warn (log but keep the row), drop (remove the bad row), fail (stop the pipeline). Example: @dlt.expect_or_drop("valid_fare", "fare > 0").

10. What is the difference between cloudFiles and spark.readStream on Delta? > cloudFiles reads raw files (JSON, CSV, Parquet) from cloud storage β€” it's Auto Loader. spark.readStream on Delta reads changes from an existing Delta table β€” it's Delta streaming. Use cloudFiles for initial file ingestion; readStream on Delta for downstream transformations.

11. What is structured streaming in Databricks? > Apache Spark's stream processing engine that treats streaming data as a continuously growing table. Uses the same DataFrame API as batch. Guarantees exactly-once processing via checkpointing. Supports sources like Kafka, Delta, Auto Loader.

12. What is a checkpoint in Spark Structured Streaming? > A location (typically cloud storage) where the streaming query saves its progress β€” which offsets have been processed, intermediate state. Enables exactly-once by resuming from the last checkpoint after failure. Never delete checkpoints of running pipelines.

13. What is the trigger mode availableNow vs processingTime? > availableNow: processes all available data then stops (like batch, but with streaming guarantees). processingTime('5 minutes'): runs continuously, triggering a micro-batch every 5 minutes. Use availableNow for scheduled batch-style jobs; processingTime for continuous low-latency streaming.

14. What is idempotency and why is it important in ETL pipelines? > An operation is idempotent if running it multiple times produces the same result as running it once. Critical for ETL because failures and retries are common β€” if an ingestion job fails halfway and retries, idempotency prevents duplicate data. MERGE is idempotent; INSERT without dedup is not.

15. What is the difference between batch and streaming ETL? > Batch: processes a fixed set of data at scheduled intervals (e.g., daily). Streaming: processes data continuously as it arrives (seconds/minutes latency). Batch is simpler and cheaper; streaming gives lower latency. Many pipelines use hybrid: streaming ingestion (Bronze) + batch transformation (Silver/Gold).

16. What is an ETL pipeline vs an ELT pipeline? > ETL: Extract, Transform, Load β€” transform data BEFORE loading into the target. ELT: Extract, Load, Transform β€” load raw data first, then transform in the target system. Lakehouse favors ELT β€” load raw data into Bronze (cheap storage), then transform using Spark's compute power.

17. What are the three DLT expectation actions: warn, drop, fail? > warn (@dlt.expect): log the violation as a metric but keep the row. drop (@dlt.expect_or_drop): silently remove rows that violate. fail (@dlt.expect_or_fail): stop the entire pipeline on violation. Choose based on criticality: warn for minor issues, drop for known bad data, fail for critical integrity violations.

18. What is the foreachBatch sink in Structured Streaming? > A sink that gives you a regular DataFrame for each micro-batch, letting you apply arbitrary logic (MERGE, multi-table writes, API calls). Example: stream.writeStream.foreachBatch(lambda df, id: df.write.mergeInto(...)). Use when the standard Delta sink (append-only) is insufficient.

19. What is event-time processing vs processing-time in streaming? > Event-time: use the timestamp embedded in the data (when the event occurred). Processing-time: use the time when the system processes the event. Event-time is more accurate for analytics but requires handling late-arriving data. Processing-time is simpler but skews results when events arrive out of order.

20. What is watermarking in Spark Structured Streaming? > A mechanism that tells Spark how late data can arrive. Example: .withWatermark("event_time", "2 hours") means data arriving more than 2 hours late is dropped. Enables Spark to bound state size for aggregations. Without watermarking, state grows unbounded and eventually OOMs.

L2

  1. Explain how Auto Loader's file notification mode works vs directory listing mode. When do you use each?

    Key points: Directory listing: polls storage for new files (simple, works everywhere, slow for millions of files). File notification: uses cloud events (Azure Event Grid / AWS SQS) to detect new files instantly (faster, but requires setup of event subscriptions). Use directory listing for small directories (<10K files). Use file notification for high-throughput ingestion (millions of files, sub-minute latency needed).

2. How do you handle schema evolution with Auto Loader (cloudFiles.schemaEvolutionMode)?

Key points: addNewColumns β€” automatically adds new columns to the schema and target table. failOnNewColumns β€” fails the pipeline on unknown columns (strict mode). rescue β€” captures unrecognized columns in a _rescued_data JSON column (safest for Bronze). none β€” ignores unknown columns. Best practice: use rescue at Bronze (never lose data), enforce schema at Silver.

3. Compare Delta Live Tables (DLT) vs hand-coded Structured Streaming pipelines β€” trade-offs?

Key points: DLT pros: declarative (less code), built-in quality expectations, automatic dependency management, managed retries. DLT cons: less flexibility, can't use arbitrary Python libraries easily, harder to debug, opaque scheduling. Hand-coded pros: full control, easier debugging, use any library. Hand-coded cons: must manage checkpoints, retries, and orchestration yourself. Use DLT for standard ETL; hand-coded for complex custom logic.

4. Explain how to implement SCD Type 2 using MERGE INTO with Delta Lake. What are the key columns?

Databricks owns pipeline orchestration, data-quality checks, and monitoring around the dimension load. Delta owns the deterministic match and history-transition mechanics; see the canonical SCD Type 2 MERGE answer.

5. How does DLT handle pipeline failures and retries? What is the concept of "idempotent recomputation"?

Key points: DLT tracks progress via checkpoints. On failure, it resumes from the last successful checkpoint. Streaming tables reprocess only new data. Materialized views are fully recomputed (idempotent by nature). "Idempotent recomputation" means you can safely rerun any part of the pipeline without duplicating data or producing incorrect results. DLT manages this automatically; in hand-coded pipelines, you must ensure idempotency yourself.

6. Compare trigger(availableNow=True) vs trigger(processingTime='5 minutes') β€” when to use each?

Key points: availableNow: process all available data in one batch, then stop. Best for scheduled jobs (e.g., hourly) where you want streaming semantics (exactly-once, schema evolution) but batch execution (start, process, stop). processingTime: run continuously, trigger every N minutes. Best for near-real-time dashboards where data must be fresh within minutes. availableNow is more cost-efficient (no idle cluster); processingTime gives lower latency.

7. Explain the role of Bronze, Silver, and Gold layers in terms of data quality, latency, and consumers.

Key points: Bronze: raw, append-only, minimal latency (seconds-minutes), consumed by data engineers for debugging. Silver: cleansed, conformed, moderate latency (minutes-hours), consumed by data engineers and data scientists. Gold: business aggregations, higher latency (hours), consumed by BI tools and business analysts. Data quality increases from Bronze (raw) to Gold (validated). Latency also increases because each layer adds processing time.

8. How does watermarking work in Structured Streaming? What happens to late-arriving data?

Key points: Watermark = max event time seen so far minus the threshold (e.g., 2 hours). Data with event_time older than the watermark is dropped (not processed). This allows Spark to discard old state for windowed aggregations, preventing unbounded memory growth. Trade-off: shorter watermark = less memory but more dropped late data; longer watermark = more late data captured but higher memory usage.

9. Compare CDC patterns: log-based CDC (Debezium/Kafka) vs query-based CDC vs timestamp-based CDC.

Key points: Log-based (Debezium): reads database transaction logs, captures all changes including deletes, minimal source impact, real-time. Query-based: compares full snapshots to detect changes, detects deletes, heavy source impact, batch only. Timestamp-based: filters by modified_date > last_run, lightweight, but misses deletes and requires a reliable timestamp column. Log-based is best for production CDC; timestamp-based as a fallback when log access is unavailable.

10. How do you handle exactly-once semantics in a Databricks streaming pipeline? > Key points: Three components: (1) Checkpointing β€” records which offsets have been processed, (2) Idempotent sink β€” Delta Lake's transaction log ensures duplicate writes are detected, (3) MERGE for upserts β€” matched rows update, unmatched insert, running twice is safe. Also: use foreachBatch + MERGE for exactly-once writes to Delta. Never delete checkpoints of active pipelines. Use availableNow for batch-like exactly-once semantics.

11. Explain foreachBatch β€” when would you use it over a standard Delta sink? > Key points: Standard Delta sink only supports append mode. foreachBatch gives you a regular DataFrame per micro-batch, enabling: (1) MERGE (upserts), (2) multi-table writes (write to both Silver and audit table), (3) calling external APIs, (4) complex conditional logic. Trade-off: more code to write and maintain. Use standard sink for simple appends; foreachBatch for everything else.

12. How do DLT expectations compare to Great Expectations or other data quality frameworks? > Key points: DLT expectations: built into the pipeline, real-time (per-batch), lightweight syntax, limited to SQL predicates. Great Expectations: separate framework, richer checks (statistical profiling, distribution analysis), more setup. DLT is best for in-pipeline data quality gates. Great Expectations is best for comprehensive data profiling and testing outside the pipeline. They're complementary, not competing.

13. What are the different ways to orchestrate dependent DLT pipelines? > Key points: (1) Databricks Workflows: create a multi-task job where each task runs a DLT pipeline, with depends_on between tasks. (2) Table-triggered jobs: Pipeline B triggers automatically when Pipeline A writes to a table. (3) Lakeflow pipeline dependencies (within a single DLT pipeline, tables auto-depend based on data lineage). (4) External orchestrator (Airflow) calling Databricks API. Best practice: Workflows for simple dependencies, table-triggered for event-driven.

14. Explain how Auto Loader handles file deduplication. What is the RocksDB state store? > Key points: Auto Loader tracks which files have been processed in a RocksDB-backed state store (persisted in the checkpoint directory). Each file path is recorded after successful processing. If a file is seen again (retry, re-delivery), it's skipped. RocksDB is an embedded key-value store optimized for write-heavy workloads β€” handles millions of file entries efficiently. Never delete the checkpoint or you lose dedup state and may reprocess files.

15. How do you test ETL pipelines in Databricks? What frameworks do you use? > Key points: Unit tests: pytest with pandas DataFrames (test transformation functions, no Spark needed). Integration tests: Nutter framework (runs notebooks on a cluster, asserts on outputs). Data quality: DLT expectations (in-pipeline) or Great Expectations (standalone). Performance tests: benchmark on staging with prod-scale data. CI: run unit tests in Azure DevOps (no cluster), integration tests on staging cluster.

16. Explain the difference between a complete output mode, append mode, and update mode in streaming. > Key points: Append: only new rows are written to the sink (most common for raw ingestion). Complete: entire result table is written every micro-batch (used for aggregations where all results change). Update: only changed rows are written (used for aggregations, but sink must support upserts). Append is default and most efficient. Complete is expensive but necessary for global aggregations. Update is a middle ground for partial aggregation updates.

17. How do you monitor and alert on streaming pipeline lag in Databricks? > Key points: Metrics to monitor: (1) inputRowsPerSecond vs processedRowsPerSecond (if input > processed, lag is growing), (2) numInputRows per batch (sudden spikes), (3) batch duration vs trigger interval (if batch takes longer than trigger, backpressure). Tools: Spark Streaming UI, Databricks SQL dashboard on system tables, custom metrics via StreamingQueryListener, PagerDuty/Slack alerts for lag exceeding threshold.

18. What is the APPLY CHANGES INTO syntax in DLT and when do you use it? > Key points: DLT's built-in CDC handler. APPLY CHANGES INTO LIVE.silver_customers FROM STREAM(LIVE.bronze_cdc) KEYS (customer_id) SEQUENCE BY (updated_at) STORED AS SCD TYPE 2. Handles SCD Type 1 and Type 2 automatically β€” no manual MERGE logic needed. Use when ingesting CDC events (from Debezium, Kafka, etc.) into Silver layer. Replaces hundreds of lines of manual MERGE code.

19. How do you handle out-of-order events in a Medallion Architecture? > Key points: Bronze: accept everything in order of arrival (append-only, no dedup). Silver: use watermarking to handle late arrivals within a tolerance window. Use MERGE with event-time ordering to ensure the latest event wins. Gold: recompute aggregations to account for late corrections. Pattern: "accept at Bronze, reconcile at Silver, correct at Gold." Trade-off between latency and correctness.

20. Explain incremental data loading patterns: append-only vs upsert vs full refresh. > Key points: Append-only: INSERT INTO β€” fastest, no dedup, good for immutable event data. Upsert: MERGE INTO β€” handles both new and updated rows, more expensive but correct for mutable data. Full refresh: INSERT OVERWRITE β€” recomputes everything, simplest but most expensive. Choose based on data mutability: events = append, dimensions = upsert, small lookup tables = full refresh.

L3 β€” Scenario-Based Questions

  1. Oracle CDC Pipeline: Design a CDC pipeline from Oracle to Delta Lake for a travel platform's booking data. Oracle does not support log-based CDC natively. What approach do you take?

    ⚠️ Common Trap
    Approach: Use Oracle GoldenGate or Debezium with Oracle LogMiner to capture changes from redo logs. Route changes to Azure Event Hubs (Kafka-compatible). Key decisions: Bronze = Auto Loader from Event Hubs (append raw CDC events), Silver = APPLY CHANGES INTO for SCD Type 2 on dimensions / MERGE for facts, Gold = materialized views for reporting. Code: spark.readStream.format("cloudFiles").option("cloudFiles.format", "json").load("abfss://cdc-events/oracle/"). Traps: Oracle LogMiner has performance overhead — test on non-prod first. Handle Oracle-specific data types (NUMBER, DATE) in the Bronze→Silver transformation.
  2. Late-Arriving Data: Flight booking amendments arrive 48 hours after the original booking. Design a pipeline that correctly handles these late-arriving events in the Medallion Architecture.

    ⚠️ Common Trap
    Approach: Bronze = append all events with event timestamp. Silver = MERGE with booking_id as key, event_time as sequence β€” latest event wins. Set watermark to 72 hours. Key decisions: Use MERGE (not append) at Silver to handle amendments. Use qualified_for_reprocessing flag for events outside the watermark. Gold = recompute daily for the last 3 days to capture late amendments. Traps: Don't drop late events at Bronze β€” always accept everything. Don't use processing-time watermarks β€” use event-time.
  3. SCD Type 2 at Scale: You need to maintain SCD Type 2 on a customer dimension table with 500M rows, receiving 2M updates daily. Design the MERGE strategy for performance.

    ⚠️ Common Trap
    Approach: (1) Partition target by is_current (most queries only need current records), (2) MERGE ON includes is_current = true (prunes 95% of target), (3) Z-ORDER or Liquid Clustering on customer_id, (4) Deduplicate source before MERGE, (5) Enable Photon for 3-5x speedup. Code: Pre-filter source to only changed records (hash comparison). Traps: Without is_current filter, MERGE scans all 500M rows including historical records. Without dedup, duplicate source rows cause incorrect multiple matches.
  4. DLT Pipeline Failure: Your DLT pipeline fails at the Silver layer due to a data quality expectation violation at 2 AM. 50,000 records were dropped. How do you investigate, recover, and prevent recurrence?

    ⚠️ Common Trap
    Approach: (1) Check DLT event log for which expectation failed and sample violations, (2) Trace back to Bronze β€” was the source data bad or was the transformation wrong?, (3) Fix: if source data, add @dlt.expect (warn) instead of @dlt.expect_or_drop, quarantine bad records to a separate table, (4) Reprocess by resetting the checkpoint. Key decisions: Quarantine > Drop for production β€” never silently lose data. Traps: Don't just weaken the expectation β€” investigate why the data was bad. It could signal a source system issue.
  5. Auto Loader Schema Drift: A source system renames columns from camelCase to snake_case overnight. Your Auto Loader pipeline breaks. Design a resilient schema evolution strategy.

    ⚠️ Common Trap
    Approach: (1) Immediate: use cloudFiles.schemaEvolutionMode = "rescue" β€” unrecognized columns go to _rescued_data, (2) Add a Silver transformation that maps both naming conventions, (3) Long-term: schema registry or contract with the source team. Key decisions: Bronze should never fail on schema changes β€” use rescue mode. Silver applies the column mapping. Traps: addNewColumns won't help here β€” it adds NEW columns but doesn't handle renames. You end up with both old and new column names.
  6. Multi-Source Medallion: You have 20 source systems feeding Bronze. Some are batch (daily files), some are streaming (Kafka). Design the Medallion Architecture to unify them.

    ⚠️ Common Trap
    Approach: Bronze = one table per source (auto_loader for files, readStream for Kafka). Silver = unified domain tables (MERGE from multiple Bronze tables). Gold = cross-domain aggregations. Key decisions: Normalize schemas at Silver, not Bronze. Use a consistent naming convention: bronze_{source}_{entity}. Handle different arrival cadences β€” batch sources land daily, streaming continuously. Traps: Don't merge all sources into one Bronze table β€” different schemas, different arrival times. Keep Bronze tables source-specific.
  7. Streaming Backpressure: Your streaming pipeline is processing 100K events/sec but the source is producing 500K events/sec. The lag keeps growing. How do you diagnose and fix this?

    ⚠️ Common Trap
    Approach: (1) Check processedRowsPerSecond in Spark Streaming UI, (2) Check task durations for data skew, (3) Scale up: more workers, larger VMs, (4) Optimize: simplify transformations, reduce shuffles, enable Photon, (5) Scale out: partition the source (multiple streams reading different partitions). Key decisions: If skewed, fix the skew first (salting). If uniformly slow, add workers. If I/O-bound, increase parallelism. Traps: Adding workers doesn't help if one partition has all the data (skew). Also check if the sink is the bottleneck (slow writes to Delta).
  8. GDPR Delete Pipeline: A GDPR deletion request arrives for a passenger. You need to delete their data across Bronze, Silver, and Gold layers in a Lakehouse. Design the process.

    ⚠️ Common Trap
    Approach: (1) Delete from all layers: DELETE FROM table WHERE passenger_id = 'X' (Bronze, Silver, Gold), (2) VACUUM with 0-hour retention on affected tables to physically remove data, (3) Log the deletion in an audit table (who, when, which tables), (4) Handle Delta CDF β€” ensure deleted records don't leak via change feeds. Key decisions: Order matters β€” delete Gold first (prevents queries on deleted data), then Silver, then Bronze. Traps: VACUUM 0 hours breaks Time Travel β€” only do this for GDPR tables. Don't forget external copies (backups, downstream exports).
  9. Deduplication Strategy: Your Kafka source sends duplicate booking events. Design a deduplication strategy at the Bronze and Silver layers that guarantees exactly-once processing.

    ⚠️ Common Trap
    Approach: Bronze = append all (including duplicates) with an ingestion_timestamp. Silver = deduplicate using MERGE: MERGE INTO silver USING (SELECT *, ROW_NUMBER() OVER (PARTITION BY booking_id ORDER BY event_time DESC) as rn FROM bronze WHERE rn = 1) source ON silver.booking_id = source.booking_id. Key decisions: Keep duplicates at Bronze for auditability. Deduplicate at Silver using event-time ordering. Use dropDuplicatesWithinWatermark for streaming dedup. Traps: Don't deduplicate at Bronze β€” you lose the ability to debug duplicate delivery issues.
  10. Testing & Validation: How would you set up automated testing for a Databricks DLT pipeline? Include unit tests, integration tests, and data quality assertions.

    ⚠️ Common Trap
    Approach: (1) Unit: extract transformation logic into Python modules, test with pytest + pandas, (2) Integration: deploy DLT pipeline to staging with sample data, assert output schema, row counts, and checksums, (3) Quality: @dlt.expect_or_fail for critical rules, @dlt.expect for monitoring rules, (4) CI/CD: unit tests in Azure DevOps (no cluster), integration tests on staging cluster. Traps: Don't only test happy paths β€” include null handling, schema evolution, and empty batches.
  11. Hybrid Batch-Streaming: You need near-real-time dashboards (5-minute latency) but also end-of-day reconciliation reports. Design a single pipeline architecture.

    ⚠️ Common Trap
    Approach: Bronze = streaming ingestion (Auto Loader, continuous). Silver = streaming MERGE with trigger(processingTime='2 minutes') for near-real-time. Gold (dashboards) = streaming materialized view refreshed every 5 minutes. Gold (reconciliation) = separate batch job running at midnight with trigger(availableNow=True) that does full-day reconciliation. Key decisions: Share Bronze and Silver between streaming and batch. Only Gold differs. Traps: Don't build two separate pipelines β€” share the common layers.
  12. Multi-Hop Streaming: Design a streaming pipeline with three hops (Bronze->Silver->Gold) where each layer applies different transformations. How do you manage checkpoints and failure recovery?

    ⚠️ Common Trap
    Approach: Each hop has its own streaming query with its own checkpoint directory: checkpoint/bronze/, checkpoint/silver/, checkpoint/gold/. Bronze reads from source, Silver reads from Bronze Delta table, Gold reads from Silver Delta table. Key decisions: Separate checkpoints per hop. If Silver fails, only Silver restarts β€” Bronze and Gold are independent. Use trigger(availableNow=True) for scheduled runs or processingTime for continuous. Traps: Never share checkpoint directories between different streaming queries. If you delete a checkpoint, the query reprocesses ALL data from the beginning.
  13. Slowly Changing Dimension with Deletes: Your source system sends hard deletes (records simply disappear). How do you detect and handle these in an SCD Type 2 pipeline?

    ⚠️ Common Trap
    Approach: (1) Detect deletes by comparing current source snapshot to Silver dimension, (2) Records in Silver but NOT in source = deleted, (3) Close the deleted records: set end_date = current_date, is_current = false, delete_flag = true. Code: MERGE INTO silver USING source ON silver.id = source.id WHEN NOT MATCHED BY SOURCE AND silver.is_current = true THEN UPDATE SET end_date = current_date(), is_current = false. Traps: This requires a FULL source snapshot for comparison β€” not suitable for CDC-only sources. Schedule delete detection as a separate batch job.
  14. Cost-Efficient Ingestion: You ingest 10 TB/day of raw JSON from ADLS Gen2. Design an ingestion pipeline that minimizes compute cost while maintaining <15 min latency.

    ⚠️ Common Trap
    Approach: Auto Loader with file notification mode (event-driven, no polling cost). Use cloudFiles.maxFilesPerTrigger to control batch size. Serverless compute for the ingestion job (pay per use, no idle cost). trigger(processingTime='5 minutes') for <15 min latency. Key decisions: File notification mode over directory listing (faster for high volume). Serverless over provisioned cluster (bursty workload). Store as Delta with auto-compaction enabled. Traps: Don't use directory listing mode for 10 TB/day β€” too many files, listing becomes the bottleneck.
  15. Pipeline Dependency Management: You have 50 DLT pipelines with complex dependencies. Some must run sequentially, others can be parallel. How do you orchestrate this?

    ⚠️ Common Trap
    Approach: Use Databricks Workflows with a multi-task DAG. Group pipelines by dependency: (1) Tier 1 = Bronze ingestion (all parallel), (2) Tier 2 = Silver transformations (depend on their respective Bronze), (3) Tier 3 = Gold aggregations (depend on multiple Silver tables). Key decisions: Use table-triggered jobs for event-driven dependencies. Use Workflows for time-based scheduling. Consider a fan-out/fan-in pattern: Tier 1 fans out (parallel), Tier 2 depends on specific Tier 1 tasks, Tier 3 fans in (depends on all Tier 2). Traps: Don't create one giant pipeline with 50 tasks β€” split into logical groups. Don't create circular dependencies.
Advanced

Databricks Platform and Governance

#

Databricks Platform and Governance

Answer First: A governed Databricks platform separates identity, storage credentials, securables, privileges, lineage, and audit evidence across environments.

Memory Map: identity -> metastore -> catalog -> schema -> object -> privilege -> audit.

Delta ownership: This module keeps Databricks integration context concise. Transaction-log, MERGE, time-travel, CDF, and maintenance internals are canonical on /learn/delta.

Azure Databricks Platform & Governance

πŸ’‘ Interview Tip
Time: 6-7 hours | Priority: HIGH β€” Platform knowledge differentiates senior from mid-level Context: Azure cloud, GDPR compliance, multi-tenant data sharing, PII governance Approach: Every topic starts with simple explanation β†’ then interview-level depth

SECTION 1: UNITY CATALOG (1.5 hours)

Answer First: In Databricks, you have hundreds of tables, ML models, files, and functions. Unity Catalog is the single place that manages ALL of them β€” who can access what, where data came from, how it's organized, and what changed.

Memory Map: Unity Catalog? What is the object hierarchy -> unity catalog object hierarchy selects the securable and privilege evaluation path -> account identity enters the securable hierarchy -> inherited or direct privilege is evaluated -> object access is allowed or denied -> lineage and audit events prove enforcement [DB_03_Azure_Platform_Governance.md:11].

Q1: What is Unity Catalog? What is the object hierarchy?

Simple Explanation: In Databricks, you have hundreds of tables, ML models, files, and functions. Unity Catalog is the single place that manages ALL of them β€” who can access what, where data came from, how it's organized, and what changed.

Think of Unity Catalog as the security guard + librarian + receptionist of your entire data platform:

  • Security guard: Controls who can access which tables (access control)
  • Librarian: Organizes all data into catalogs/schemas so you can find it easily (discovery)
  • Receptionist: Keeps a log of who accessed what and when (auditing)

Why do we need it? Without Unity Catalog: Each team creates tables in random locations, no one knows who has access to what, PII data leaks because there's no control, and you can't trace where data came from. With Unity Catalog: One central place to govern everything β€” tables, ML models, files, permissions.

The hierarchy (how data is organized):

πŸ—‚οΈMetastore (top level β€” ONE per Azure region)
β”‚ Think of this as: "The entire data universe for your region"
Catalog (logical grouping β€” like separate departments)
β”‚ Examples: travel_prod, travel_dev, travel_staging
β”‚ Think of this as: "A database server" or "an environment"
Schema (like a database inside the catalog)
β”‚ Examples: bookings, passengers, flights, analytics
β”‚ Think of this as: "A database" or "a topic area"
Table (your data β€” managed or external)
View (a saved query on top of tables)
Function (reusable SQL/Python functions)
Model (ML models registered here)
Volume (unstructured files β€” CSVs, images, PDFs)

Three-level namespace β€” how you reference any table:

sql
SELECT * FROM travel_prod.bookings.fact_flights;
--           ^^^^^^^^^^^  ^^^^^^^^  ^^^^^^^^^^^^
--           catalog      schema    table
-- Read as: "In the travel_prod catalog, bookings schema, fact_flights table"

Why 3 levels? Before Unity Catalog, Databricks used Hive Metastore with only 2 levels (database.table). With 3 levels, you can separate environments (prod/dev), domains (bookings/flights), AND tables β€” much better organization.

Six pillars of Unity Catalog (what it does):

  1. Access Control β€” GRANT/REVOKE permissions at any level (catalog, schema, table, column)
  2. Discovery β€” Search, browse, preview all data assets in one place
  3. Lineage β€” Automatically tracks which table feeds into which table, column by column
  4. Auditing β€” Logs every access: who read what table, when, from which notebook
  5. Quality Monitoring β€” Detects anomalies in data quality automatically (new 2026)
  6. Data Sharing β€” Share data with external partners via Delta Sharing protocol

Interview tip: When asked about Unity Catalog, don't just describe the hierarchy. Mention the 6 pillars β€” it shows you understand it's not just a "table organizer" but a full governance platform.

Answer First: Column-level security (column masking): Different users see different values for the SAME column. For example, the HR team sees full email addresses, but the analytics team sees masked emails (kri @gmail.com).

Memory Map: row-level and column-level security -> row level and column level security determines the attribute-driven row or column policy -> classification tag describes the asset -> identity attributes select a policy -> row or column transformation enforces it -> impersonation test verifies the result [DB_03_Azure_Platform_Governance.md:68].

Q2: What is row-level and column-level security?

Simple Explanation:

Column-level security (column masking): Different users see different values for the SAME column. For example, the HR team sees full email addresses, but the analytics team sees masked emails (kri***@gmail.com).

Row-level security (row filtering): Different users see different ROWS from the same table. For example, the Lufthansa team sees only Lufthansa bookings, the Air India team sees only Air India bookings.

Why do we need this? A travel platform has multiple airline partners. Each airline should ONLY see their own bookings. And passenger PII (email, phone) should be hidden from teams that don't need it (GDPR compliance).

sql
-- ============================================
-- COLUMN MASKING: Hide passenger email based on user's team
-- ============================================

-- Step 1: Create a masking function
-- This function decides what each user sees for the "email" column
CREATE FUNCTION mask_email(email STRING)
RETURN CASE
    WHEN is_member('gdpr_admins') THEN email
    -- GDPR admins see the full email: "krishna@gmail.com"

    WHEN is_member('booking_agents') THEN CONCAT(LEFT(email, 3), '***@', SPLIT(email, '@')[1])
    -- Booking agents see partial: "kri***@gmail.com"

    ELSE '***@***.***'
    -- Everyone else sees fully masked: "***@***.***"
END;

-- Step 2: Apply the mask to the column
ALTER TABLE passengers ALTER COLUMN email SET MASK mask_email;
-- Now every query on passengers.email automatically applies the mask!
-- No code changes needed in any notebook or report.


-- ============================================
-- ROW-LEVEL SECURITY: Airlines see only their own bookings
-- ============================================

-- Step 1: Create a filter function
-- This function decides which ROWS each user can see
CREATE FUNCTION airline_access(airline_code STRING)
RETURN CASE
    WHEN is_member('travelco_admins') THEN TRUE
    -- Admins see ALL rows (all airlines)

    WHEN is_member('lufthansa_team') THEN airline_code = 'LH'
    -- Lufthansa team sees only rows where airline_code = 'LH'

    WHEN is_member('air_india_team') THEN airline_code = 'AI'
    -- Air India team sees only rows where airline_code = 'AI'

    ELSE FALSE
    -- Everyone else sees NO rows
END;

-- Step 2: Apply the filter to the table
ALTER TABLE bookings SET ROW FILTER airline_access ON (airline_code);
-- Now when Lufthansa user queries bookings, they automatically see only LH rows!
-- The filter is applied at the database level β€” can't be bypassed.

Example use case: "200+ airline partners each access only their own booking data via row-level security, while passenger PII is masked for non-authorized teams. This is all configured in Unity Catalog β€” no application-level code needed."

Interview tip: Mention that these security policies are applied at the catalog level β€” so even if someone writes new SQL or connects via Power BI, the rules still apply. It can't be bypassed.

Answer First: Data lineage records upstream sources, downstream consumers, and transformations between them. Unity Catalog uses that graph for impact analysis, discovery, and audit evidence.

Memory Map: data lineage? How does Unity Catalog track it -> supported query capture records table and column relationships -> graph stores upstream sources and downstream consumers -> catalog interface exposes impact paths -> audit lineage confirms observed transformations [DB_03_Azure_Platform_Governance.md:136].

Q3: What is data lineage? How does Unity Catalog track it?

Simple Explanation: Data lineage answers the question: "Where did this data come from?" and "What depends on this data?"

Unity Catalog automatically tracks lineage at the column level. You don't need to set anything up β€” it just works. Every time a notebook, job, or Lakeflow pipeline reads from table A and writes to table B, Unity Catalog records that connection.

Real-world analogy: Think of lineage like a family tree for your data. You can trace any piece of data back to its source (parents) and see what it feeds into (children).

Example:

Oracle bookings→Bronze.raw_bookings → Silver.clean_bookings → Gold.daily_revenue
β†’ Gold.passenger_360

If someone asks "Where does the daily_revenue.total_fare column come from?", lineage shows: daily_revenue.total_fare ← Silver.clean_bookings.fare_amount ← Bronze.raw_bookings.fare ← Oracle.BOOKING_AMT

Use cases:

  1. Impact analysis: "If we rename the fare_amount column in Silver, which Gold tables will break?"
  2. Root cause debugging: "Gold revenue report shows wrong numbers β€” let me trace back to see where the data comes from"
  3. GDPR compliance: "Show me ALL tables that contain passenger email β€” I need to delete a passenger's data"
  4. Data quality: "Null values appearing in Gold β€” lineage shows they come from a specific Bronze source file"

How to view: Open Catalog Explorer in Databricks UI β†’ click on any table β†’ click "Lineage" tab.

Answer First: When you create an external table (data stored in YOUR ADLS Gen2 account, not managed by Databricks), Unity Catalog needs a way to access that storage. This is done through a chain.

Memory Map: Azure external storage access -> managed identity enters a storage credential -> external location restricts the ADLS path -> catalog grants and Azure audit logs verify least privilege [DB_03_Azure_Platform_Governance.md:163].

Q4: What are Storage Credentials and External Locations?

Simple Explanation: When you create an external table (data stored in YOUR ADLS Gen2 account, not managed by Databricks), Unity Catalog needs a way to access that storage. This is done through a chain:

  1. Storage Credential = "Here are the keys to my cloud storage" (Azure Service Principal or Managed Identity)
  2. External Location = "Use those keys to access THIS specific folder" (maps a storage path to the credential)
  3. External Table = "Create a table pointing to data at that location"

Real-world analogy:

  • Storage Credential = Master key to the building (access to Azure storage account)
  • External Location = Permission to enter a specific room (access to a specific folder in ADLS)
  • External Table = A desk in that room (a table pointing to files in that folder)
πŸ—‚οΈChain: Storage Credential β†’ External Location β†’ External Table
Storage Credential:
What: Azure Service Principal or Managed Identity credentials
Why: Gives Databricks permission to access your ADLS Gen2 storage
Who creates: Account admin (one-time setup)
Scope: Can be used by multiple External Locations
External Location:
What: Maps a specific ADLS path to a Storage Credential
Why: Controls which folders Databricks can access
Example: "abfss://raw@travelcostorage.dfs.core.windows.net/" β†’ uses credential X
Who creates: Metastore admin
sql
-- Step 1: Create a storage credential (admin task β€” done once)
CREATE STORAGE CREDENTIAL travelco_adls_cred
WITH AZURE_MANAGED_IDENTITY (
    access_connector_id = '/subscriptions/.../accessConnectors/databricks-connector'
);
-- This tells Databricks: "Use this Azure Managed Identity to access ADLS"

-- Step 2: Create an external location (map a folder to the credential)
CREATE EXTERNAL LOCATION travelco_raw_data
URL 'abfss://raw@travelcostorage.dfs.core.windows.net/'
WITH (STORAGE CREDENTIAL travelco_adls_cred);
-- This tells Databricks: "You can access this specific ADLS folder using that credential"

-- Step 3: Create an external table at that location
CREATE TABLE travel_prod.bookings.legacy_flights
LOCATION 'abfss://raw@travelcostorage.dfs.core.windows.net/legacy/flights/';
-- This creates a table pointing to existing data files in ADLS
-- Data lives in YOUR storage β€” Databricks just reads it

When do you need this? When migrating from existing data lakes, when data is shared with other systems, or when your company's policy requires data to stay in their own storage accounts.

Answer First: It had only 2 levels (database.table), limited security, and no lineage. Many companies need to migrate from Hive to Unity Catalog.

Memory Map: Hive-to-Unity assessment -> inventory tables, paths, and workspace grants -> map owners into catalogs and account groups -> UCX findings establish the migration backlog [DB_03_Azure_Platform_Governance.md:218].

Q5: How do you migrate from Hive Metastore to Unity Catalog?

Simple Explanation: Before Unity Catalog existed, Databricks used Hive Metastore (the old system) to manage tables. It had only 2 levels (database.table), limited security, and no lineage. Many companies need to migrate from Hive to Unity Catalog.

Why migrate? Hive Metastore has no row-level security, no column masking, no lineage, no ABAC, no data sharing. Unity Catalog has all of these.

πŸ—‚οΈMigration Steps (500+ tables):
1. PREPARATION (Week 1)
Audit all existing Hive tables: how many? managed or external?
Plan the naming: hive_metastore.bookings.* β†’ travel_prod.bookings.*
β”‚ Old: SELECT * FROM bookings.fact_flights
β”‚ New: SELECT * FROM travel_prod.bookings.fact_flights (3-level namespace)
Review existing permissions (who has access to what)
Plan catalogs: travel_prod, travel_dev, travel_staging
2. INFRASTRUCTURE (Week 2)
Create Unity Catalog metastore β†’ assign to Azure workspace
Create storage credentials for ADLS Gen2 (Service Principal)
Create external locations (for external table data)
Create catalogs and schemas matching your naming plan
3. TABLE MIGRATION (Weeks 3-5)
Managed tables: Copy data using CTAS (Create Table As Select)
β”‚ CREATE TABLE travel_prod.bookings.fact_flights
β”‚ AS SELECT * FROM hive_metastore.bookings.fact_flights;
External tables: Just re-point to existing ADLS path (no data copy!)
β”‚ CREATE TABLE travel_prod.bookings.legacy_data
β”‚ LOCATION 'abfss://existing/path/in/adls/';
Tool: Databricks UCX (Unity Catalog Migration tool) automates most of this
Validate: Compare row counts and checksums between old and new
4. CODE UPDATES (Week 5-6)
Update ALL notebooks: db.table β†’ catalog.schema.table
Update all jobs and Lakeflow pipelines
Update BI tool connections (Power BI, Tableau)
5. VALIDATION & CUTOVER (Weeks 7-8)
Run BOTH old Hive and new UC tables in parallel for 2 weeks
Compare query results β€” they must match exactly
After validation, deprecate all hive_metastore references

Interview tip: Mention the UCX tool β€” it shows you know the practical migration path, not just theory. Also mention "parallel run for 2 weeks" β€” it shows you understand production safety.

SECTION 2: NEW β€” ABAC (Attribute-Based Access Control) (45 min)

Answer First: RBAC (Role-Based Access Control) is the traditional way: you create roles and GRANT permissions on specific tables to each role. If you have 500 tables, you need 500 GRANT statements for each role. When a new table is added, you must manually GRANT access to it.

Memory Map: ABAC? How is it different from RBAC -> governance tags describe protected assets -> principal attributes enter a reusable policy -> function filters rows or masks columns -> impersonation verifies contextual access [DB_03_Azure_Platform_Governance.md:269].

Q6: What is ABAC? How is it different from RBAC?

Simple Explanation:

RBAC (Role-Based Access Control) is the traditional way: you create roles and GRANT permissions on specific tables to each role. If you have 500 tables, you need 500 GRANT statements for each role. When a new table is added, you must manually GRANT access to it.

ABAC (Attribute-Based Access Control) is the new way: you TAG tables with attributes (like "pii=true") and create ONE policy that says "only gdpr_authorized users can access tables tagged pii=true." When a new PII table is added and tagged, the policy automatically applies β€” no manual GRANT needed.

Real-world analogy:

  • RBAC = Security badges with specific room numbers: "Badge allows Room 101, 102, 103". New room added? Update every badge.
  • ABAC = Security badges with rules: "Badge allows any room tagged 'public'". New public room added? Already accessible β€” no badge update needed.

Public Preview since November 2025 β€” knowing this in your interview shows you're up to date!

AspectRBAC (Traditional)ABAC (New)
How it worksGRANT permissions on specific tablesCreate policies based on tags
Scale500 tables = 500 GRANT statements per role1 policy covers ALL tables with matching tag
New table addedMust manually GRANT accessJust TAG the table β†’ policy auto-applies
MaintenanceHigh β€” every new table needs manual grantsLow β€” tag-based, automatic
DynamicNo β€” static grantsYes β€” add/remove tags at any time
sql
-- ============================================
-- RBAC way (old β€” lots of manual work):
-- ============================================
GRANT SELECT ON TABLE passengers TO gdpr_team;
GRANT SELECT ON TABLE passenger_contacts TO gdpr_team;
GRANT SELECT ON TABLE passenger_addresses TO gdpr_team;
-- ... repeat for every PII table (500+ statements!)
-- New PII table added? Must remember to add another GRANT!

-- ============================================
-- ABAC way (new β€” one policy covers everything):
-- ============================================

-- Step 1: Tag tables with attributes
ALTER TABLE passengers SET TAGS ('pii' = 'true', 'gdpr_scope' = 'eu');
-- This table contains PII data and falls under EU GDPR

ALTER TABLE bookings SET TAGS ('pii' = 'false');
-- This table doesn't contain PII

ALTER TABLE passenger_contacts SET TAGS ('pii' = 'true', 'gdpr_scope' = 'eu');
-- Another PII table β€” just tag it!

-- Step 2: Create ONE policy that covers ALL PII tables
CREATE FUNCTION pii_access_policy()
RETURN CASE
    WHEN has_tag('pii', 'true') AND NOT is_member('gdpr_authorized') THEN FALSE
    -- If table is tagged pii=true AND user is NOT in gdpr_authorized group β†’ DENY access
    ELSE TRUE
    -- Otherwise β†’ ALLOW access
END;
-- has_tag() checks if the table being accessed has a specific tag
-- is_member() checks if the current user belongs to a specific group

-- Step 3: Apply policy to entire catalog (all tables in it)
ALTER CATALOG travel_prod SET ACCESS CONTROL USING pii_access_policy;
-- Now EVERY table in travel_prod with tag pii=true is automatically protected!
-- New PII table added? Just tag it pii=true β†’ protection is automatic!

Note: ABAC uses snake_case functions: has_tag, has_tag_value, is_member. CamelCase (hasTag) is being deprecated.

Example use case: "Instead of managing GRANT statements for 500+ tables, we tag all PII tables with pii=true and apply ONE ABAC policy. When any new table is created with that tag, it's automatically governed. This saves hours of manual access management."

SECTION 3: PHOTON ENGINE (30 min)

Answer First: Normally, Spark runs on the JVM (Java Virtual Machine). Photon is Databricks' own query engine written in C++ (a much faster language for data processing). It replaces Spark's JVM execution with native C++ code β€” making queries 3-12x faster.

Memory Map: Photon Engine -> photon engine identifies the native operator path and fallback risk -> supported operator enters native execution -> vectorized batches reduce JVM overhead -> scan or join completes with fewer CPU cycles -> operator timing validates the gain [DB_03_Azure_Platform_Governance.md:340].

Q7: What is Photon Engine?

Simple Explanation: Normally, Spark runs on the JVM (Java Virtual Machine). It works, but it's not the fastest. Photon is Databricks' own query engine written in C++ (a much faster language for data processing). It replaces Spark's JVM execution with native C++ code β€” making queries 3-12x faster.

The best part: You don't change ANY code. Same SQL, same PySpark, same notebooks. You just pick "Photon" runtime when creating your cluster, and everything runs faster automatically.

Real-world analogy: Imagine your car (Spark on JVM) gets a turbocharged engine swap (Photon in C++). Same car, same dashboard, same steering wheel β€” but much faster.

When Photon helps A LOT:

OperationSpeed ImprovementWhy
Table scans (reading data)Up to 12x fasterC++ reads data more efficiently than JVM
Aggregations (SUM, COUNT, AVG)3-10x fasterVectorized processing (processes many rows at once)
Joins3-8x fasterBetter memory management
Filters (WHERE clauses)5-10x fasterNative code skips rows faster
MERGE/UPDATE/DELETE3-5x fasterImportant for Silver layer!

When Photon does NOT help:

OperationWhy It Doesn't Help
Python UDFsUDFs run in Python process, completely bypasses Photon
RDD-based codePhoton only works with DataFrame API and SQL, not old-style RDDs
Very small data (<100 MB)The overhead of Photon startup isn't worth it for tiny datasets
ML training (MLlib)ML uses different execution path β€” use GPU clusters instead
Unsupported operationsPhoton silently falls back to Spark (no error β€” just runs slower)
python β€” editable
# HOW TO ENABLE PHOTON:

# Option 1: Select Photon runtime when creating cluster (recommended)
# In Databricks UI: Cluster β†’ Databricks Runtime β†’ select "18.1 LTS Photon"
# That's it! No code changes needed.

# Option 2: Enable via config (if cluster already exists)
spark.conf.set("spark.databricks.photon.enabled", "true")

# Your existing SQL and PySpark code runs 3-12x faster β€” no modifications!

Example answer: "We use Photon for all our Silver-layer MERGE operations on booking tables. The 3-5x speedup on MERGE reduced our daily pipeline from 3 hours to 45 minutes. No code changes were needed β€” we just switched the runtime."

Interview tip: Don't just say "Photon makes things faster." Say WHEN to use it and WHEN NOT to use it. Mention "falls back gracefully" β€” if Photon can't handle an operation, it silently uses regular Spark instead of crashing.

SECTION 4: SERVERLESS COMPUTE (45 min)

Answer First: Normally in Databricks, you create a cluster (a group of VMs/machines) to run your code. You pick the machine type, the number of machines, configure auto-scaling, etc. If the cluster sits idle for hours, you're still paying.

Memory Map: Serverless compute in Databricks -> serverless compute in databricks defines the required compute lifecycle and isolation -> workload lifecycle sets isolation and latency needs -> compute model supplies elasticity -> utilization determines effective cost -> startup and billing evidence validate fit [DB_03_Azure_Platform_Governance.md:388].

Q8: What is Serverless compute in Databricks?

Simple Explanation: Normally in Databricks, you create a cluster (a group of VMs/machines) to run your code. You pick the machine type, the number of machines, configure auto-scaling, etc. If the cluster sits idle for hours, you're still paying. If it takes 10 minutes to start up, you wait.

Serverless means: Databricks manages the machines FOR you. You just submit your query/job, and Databricks instantly provides compute resources. When you're done, resources are released. You only pay for the time your code is actually running.

Real-world analogy:

  • Standard cluster = Owning a car. You pay for insurance, parking, gas even when it's parked.
  • Serverless = Uber. You pay only when you're riding. No car maintenance, no parking fees.
AspectStandard ClustersServerless
Who manages it?You configure machine type, count, etc.Databricks manages everything
Startup time3-10 minutes (slow!)<10 seconds (instant!)
ScalingYou configure min/max workersAutomatic β€” scales up/down as needed
BillingPay for running time (EVEN when idle)Pay ONLY for execution time
Cost savingsβ€”Up to 70% savings for bursty workloads
ComplianceStandardHITRUST, PCI-DSS, TISAX (important for travel!)

What can run on Serverless?

  • SQL Warehouses (most mature β€” been around longest)
  • Notebooks (interactive development)
  • Jobs/Workflows (scheduled production pipelines)
  • Lakeflow Pipelines
  • JAR tasks (new β€” February 2026)
  • AI Runtime with GPU (Public Preview β€” March 2026)

Answer First: A regular Databricks workspace requires you to set up compute configurations, storage, networking, etc. A Serverless Workspace (GA January 2026) is a pre-configured workspace where EVERYTHING is managed β€” compute, storage, networking. You just log in and start working.

Memory Map: Serverless Workspaces -> managed workspace removes customer network and cluster setup -> platform provisions isolated execution on demand -> policy controls available features and data access -> startup and run history show managed lifecycle [DB_03_Azure_Platform_Governance.md:418].

Q9: What are Serverless Workspaces?

Simple Explanation: A regular Databricks workspace requires you to set up compute configurations, storage, networking, etc. A Serverless Workspace (GA January 2026) is a pre-configured workspace where EVERYTHING is managed β€” compute, storage, networking. You just log in and start working.

Think of it as: "Databricks out of the box β€” no setup needed."

Key features:

  • Managed compute β€” no cluster configuration needed
  • Managed storage β€” Databricks handles where data is stored
  • Instant startup β€” everything is ready in seconds
  • Auto-scaling β€” resources scale with your workload
  • Network boundary isolation β€” secure by default

When to use Serverless vs Standard:

WorkloadRecommendationWhy
SQL analytics (BI dashboards)Serverless SQL WarehouseAuto-scale, auto-suspend, no idle cost
Bursty/intermittent jobsServerlessPay only when running β€” big cost savings
Development/ad-hoc explorationServerlessInstant startup, no idle cost
24/7 streaming (always running)StandardPredictable load β†’ predictable cost is cheaper
GPU workloads (ML training)StandardGPU serverless is still in preview
Very specific hardware needsStandardServerless doesn't let you pick machine types

Answer First: Databricks has 3 types of compute. Knowing which to use WHERE is a common interview question.

Memory Map: the difference between Job Cluster, All-Purpose Cluster, and Serverless -> interactive compute supports collaborative development -> ephemeral job compute isolates scheduled production runs -> managed SQL capacity serves BI concurrency -> lifecycle latency and utilization determine fit [DB_03_Azure_Platform_Governance.md:444].

Q10: What is the difference between Job Cluster, All-Purpose Cluster, and Serverless?

Simple Explanation: Databricks has 3 types of compute. Knowing which to use WHERE is a common interview question:

All-Purpose Cluster = A long-running cluster shared by multiple users. Like a shared office computer β€” always on, multiple people use it. MOST EXPENSIVE.

Job Cluster = A cluster created just for one job and destroyed after. Like a rental car β€” rent it for the trip, return it after. CHEAPER than All-Purpose.

Serverless = No cluster at all β€” Databricks provides compute on demand. Like Uber β€” just request a ride, pay per minute. CHEAPEST for bursty work.

AspectAll-PurposeJob ClusterServerless
LifecycleRuns until you stop itCreated per job, destroyed afterOn-demand per query/task
CostMost expensive (all-purpose pricing)Cheaper (job compute pricing)Cheapest for bursty
SharingMultiple users share itSingle job onlySingle job/query
Startup timeInstant (already running)3-10 minutes (creating VMs)<10 seconds
When to useDevelopment ONLYProduction scheduled jobsBI queries, bursty workloads

Golden rule for production: NEVER use All-Purpose clusters for production pipelines. They're too expensive. Use Job Clusters or Serverless.

Example answer: "We use All-Purpose clusters only for development notebooks. Production ETL pipelines run on Job Clusters with Photon for cost efficiency. BI dashboards connect via Serverless SQL Warehouses that auto-suspend when idle β€” zero cost overnight."

SECTION 5: AZURE-SPECIFIC INTEGRATION (45 min)

Answer First: Azure Databricks has 3 main components (called "planes") that work together. Understanding this helps you answer architecture questions and troubleshoot issues.

Memory Map: Azure Databricks architecture work? (Three-plane model) -> control services manage workspace metadata -> customer workers execute workloads -> cloud object storage retains governed data -> network boundaries and logs prove separation [DB_03_Azure_Platform_Governance.md:471].

Q11: How does Azure Databricks architecture work? (Three-plane model)

Simple Explanation: Azure Databricks has 3 main components (called "planes") that work together. Understanding this helps you answer architecture questions and troubleshoot issues.

πŸ“ Architecture Diagram
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚              CONTROL PLANE                       β”‚
β”‚        (Managed by Databricks in Azure)          β”‚
β”‚                                                  β”‚
β”‚  What lives here:                                β”‚
β”‚  β”œβ”€β”€ Databricks Workspace UI (the web interface) β”‚
β”‚  β”œβ”€β”€ REST APIs (for automation)                  β”‚
β”‚  β”œβ”€β”€ Job Scheduler (runs your scheduled jobs)    β”‚
β”‚  β”œβ”€β”€ Unity Catalog Metastore (table metadata)    β”‚
β”‚  └── Cluster Manager (creates/destroys VMs)      β”‚
β”‚                                                  β”‚
β”‚  Key point: You don't manage this. Databricks    β”‚
β”‚  runs it in THEIR Azure subscription.            β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                   β”‚ Secure encrypted connection
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚              COMPUTE PLANE                       β”‚
β”‚        (In YOUR Azure subscription)              β”‚
β”‚                                                  β”‚
β”‚  What lives here:                                β”‚
β”‚  β”œβ”€β”€ VMs (cluster worker/driver nodes)           β”‚
β”‚  β”œβ”€β”€ VNet (Virtual Network for isolation)        β”‚
β”‚  β”œβ”€β”€ NSG (Network Security Groups β€” firewall)    β”‚
β”‚  └── Managed Resource Group (Databricks creates) β”‚
β”‚                                                  β”‚
β”‚  Key point: VMs run in YOUR subscription,        β”‚
β”‚  so your data never leaves your network.         β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                   β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚              STORAGE PLANE                       β”‚
β”‚        (In YOUR Azure subscription)              β”‚
β”‚                                                  β”‚
β”‚  What lives here:                                β”‚
β”‚  β”œβ”€β”€ ADLS Gen2 (your main data lake storage)     β”‚
β”‚  β”œβ”€β”€ Azure Blob Storage                          β”‚
β”‚  └── DBFS root storage (Databricks internal)     β”‚
β”‚                                                  β”‚
β”‚  Key point: Your data stays in YOUR storage.     β”‚
β”‚  Databricks never stores your data.              β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Why this matters: "Our passenger PII data never leaves our Azure subscription. Databricks Control Plane manages orchestration, but the actual data processing (Compute) and storage (ADLS Gen2) happen inside our own subscription β€” important for GDPR compliance."

Key Azure services that integrate with Databricks:

Azure ServiceWhat It Does With Databricks
ADLS Gen2Primary storage for all Delta tables (the data lake)
Azure Key VaultStores secrets (passwords, API keys) β€” Databricks reads them securely
Azure Entra ID (formerly Active Directory)Single sign-on (SSO), user provisioning, Service Principals
Azure Event HubsKafka-compatible messaging β€” used for CDC streaming
Azure DevOpsCI/CD pipelines to deploy Databricks jobs
Azure Data FactoryCan orchestrate Databricks jobs (alternative to Databricks Workflows)
Power BIConnects directly to Databricks SQL Warehouse for BI reports

Answer First: You should NEVER put passwords, API keys, or connection strings directly in your code or notebooks. Anyone who sees the notebook can see the password. Instead, store secrets in Azure Key Vault (a secure vault in Azure), and Databricks reads them at runtime.

Memory Map: Azure Key Vault integration? Why is it important -> azure key vault integration it important scopes the trusted request to one storage boundary -> workload identity requests storage -> governed credential establishes trust -> external location scopes the path -> audit event proves least privilege [DB_03_Azure_Platform_Governance.md:535].

Q12: What is Azure Key Vault integration? Why is it important?

Simple Explanation: You should NEVER put passwords, API keys, or connection strings directly in your code or notebooks. Anyone who sees the notebook can see the password. Instead, store secrets in Azure Key Vault (a secure vault in Azure), and Databricks reads them at runtime.

Real-world analogy: Instead of writing your house key code on a Post-it note stuck to the door, you keep it in a safe (Key Vault) and only authorized people can open the safe.

# BAD β€” NEVER do this! Password visible in notebook:
# oracle_password = "MySecretP@ssw0rd"
# GOOD β€” Read password from Azure Key Vault at runtime:
# Step 1: (Done ONCE by admin) Create a "secret scope" in Databricks
# that connects to your Azure Key Vault:
# databricks secrets create-scope \
# --scope travelco-secrets \
# --scope-backend-type AZURE_KEYVAULT \
# --resource-id /subscriptions/.../vaults/travelco-kv \
# --dns-name https://travelco-kv.vault.azure.net/
# Step 2: In your notebook, read secrets using dbutils:
oracle_password = dbutils.secrets.get(
scope="travelco-secrets", # Which vault to read from
key="oracle-prod-password" # Which secret to get
)
# The password is now in the variable β€” but if you try to print it,
# Databricks shows [REDACTED] in the output (security feature!)
kafka_connection = dbutils.secrets.get(
scope="travelco-secrets",
key="eventhubs-connection-string" # Connection string for Azure Event Hubs
)
# Use these variables in your JDBC connections, Kafka configs, etc.

Answer First: A Service Principal is like a "robot user" account in Azure. Instead of using a real person's login to run production jobs, you create a Service Principal (non-human identity) that represents your application.

Memory Map: Service Principals? Why use them for production -> application identity replaces a human credential -> group membership grants least privilege -> secretless federation or rotation limits exposure -> audit events attribute every request [DB_03_Azure_Platform_Governance.md:573].

Q13: What are Service Principals? Why use them for production?

Simple Explanation: A Service Principal is like a "robot user" account in Azure. Instead of using a real person's login to run production jobs, you create a Service Principal (non-human identity) that represents your application.

Why not use a real person's account?

  • What if the person leaves the company? All jobs break.
  • What if they change their password? All jobs break.
  • What if MFA (multi-factor authentication) pops up? Job can't enter the code.
  • Audit logs show "Krishna ran this job" β€” was it Krishna manually, or the scheduled job?
πŸ—‚οΈWhy Service Principals for production:
No dependency on individual employees (person leaves β†’ jobs still work)
No MFA interruptions (robots don't need phone verification)
Scoped permissions (give it ONLY what it needs β€” principle of least privilege)
Clear audit trail ("travelco-etl-sp ran this job" vs "krishna ran this job")
Required for Unity Catalog external access in production
How to set up:
1. Create Service Principal in Azure Entra ID (done by Azure admin)
2. Add the Service Principal to your Databricks workspace
3. Grant Unity Catalog permissions: GRANT SELECT ON TABLE ... TO `travelco-etl-sp`
4. Configure Job Clusters to run as this Service Principal
5. Store the SP's secret in Azure Key Vault (rotate every 90 days)

Example answer: "All our production pipelines run under Service Principals, not user accounts. This ensures jobs aren't affected when employees change teams, and audit logs clearly show which application accessed what data."

SECTION 6: DATA GOVERNANCE AT SCALE (1 hour)

Delta integration summary β€” Q14: How do you handle GDPR "Right to Be Forgotten" in Delta Lake?: Databricks governance orchestrates the subject-request workflow; Delta history, DELETE, VACUUM retention checks, and physical-erasure verification remain in the canonical Delta recovery answer. Open the canonical Delta answer.

Answer First: With 200+ engineers, you can't rely on "everyone will follow the rules." You need automated governance β€” policies that are enforced at the platform level, not just documented in a wiki.

Memory Map: you design a governance framework for 200+ data engineers -> design governance framework for 200 data engineers assigns one governance responsibility and control boundary -> ownership defines accountability -> groups and policies establish the baseline -> automation applies controls consistently -> lineage and audit review prove compliance [DB_03_Azure_Platform_Governance.md:665].

Q15: How would you design a governance framework for 200+ data engineers?

Simple Explanation: With 200+ engineers, you can't rely on "everyone will follow the rules." You need automated governance β€” policies that are enforced at the platform level, not just documented in a wiki.

πŸ—‚οΈ1. ACCESS CONTROL (Unity Catalog)
4 catalogs: travel_prod, travel_dev, travel_staging, travel_sandbox
β”‚ Developers get full access to dev and sandbox
β”‚ Only pipelines (Service Principals) write to prod
Row-level security: each airline sees only their own data
Column masking: PII hidden from teams that don't need it
ABAC: tag PII tables β†’ one policy governs all automatically
Principle of least privilege: start with no access, grant only what's needed
2. DATA CLASSIFICATION (know what data you have)
Auto-tagging (new Oct 2025 feature): Databricks auto-detects PII columns
Manual tags: pii=true, gdpr_scope=eu, sensitivity=high
Tags drive ABAC policies (tagged pii=true β†’ access restricted)
Discover page (new Feb 2026): browse data organized by business domain
3. AUDIT & COMPLIANCE (prove you're following rules)
Unity Catalog audit logs β†’ Azure Monitor (who accessed what, when)
Delta Lake DESCRIBE HISTORY: track every change to every table
GDPR deletion pipeline: automated process with compliance logging
Data retention policies: auto-delete data older than X years
4. DATA QUALITY (ensure data is correct)
Lakeflow expectations at every layer (Bronze/Silver/Gold)
Quality monitoring anomaly detection (new Feb 2026): auto-alerts
Data contracts: teams agree on schema/quality of data they share
Quarantine: bad data goes to a separate table + alert sent
5. DATA DISCOVERY (find what you need)
Unity Catalog search: find any table, view, model
Column descriptions and table comments: document what data means
Lineage visualization: see data flow from source to report
Domains (new Feb 2026): organize by business area (Bookings, Flights, Loyalty)
Certification tags (GA Oct 2025): mark trusted tables as "certified"

Answer First: Delta Sharing is a way to share data with external partners (other companies) without copying the data. The partner reads directly from YOUR storage. You control what they can access, and you see audit logs of what they read.

Memory Map: Delta Sharing -> delta sharing defines provider ownership and recipient access semantics -> provider selects governed objects -> share grants recipient access -> credentialed client reads current data -> audit and revocation preserve provider control [DB_03_Azure_Platform_Governance.md:708].

Q16: What is Delta Sharing?

Simple Explanation: Delta Sharing is a way to share data with external partners (other companies) without copying the data. The partner reads directly from YOUR storage. You control what they can access, and you see audit logs of what they read.

Why is this useful? A travel booking platform works with multiple airlines. Each airline needs access to their own booking data. Instead of creating copies for each airline (expensive, hard to keep in sync), you share the data directly using Delta Sharing.

Real-world analogy: Instead of printing copies of a report for each airline (copies get outdated), you give each airline a "viewing link" to the live report. They always see the latest data, and you control who can view what.

How it works:

1. The travel platform (Provider) shares a Delta table via Delta Sharing
2. The travel platform creates a "share" with only Lufthansa's data (filtered)
3. The travel platform gives Lufthansa a sharing profile (JSON file with credentials)
4. Lufthansa (Recipient) uses ANY tool to read the shared data:
Databricks, Spark, pandas, Power BI, Snowflake, Trino...
5. Data is read DIRECTLY from the provider's ADLS β€” no copying!
6. The provider sees audit logs: "Lufthansa read fact_bookings at 3pm"
sql
-- Step 1: Create a share (a named collection of shared tables)
CREATE SHARE airline_bookings_share;

-- Step 2: Add a table to the share β€” with FILTERING (only Lufthansa data!)
ALTER SHARE airline_bookings_share ADD TABLE travel_prod.bookings.fact_bookings
    PARTITION (airline_code = 'LH');
-- Only rows where airline_code = 'LH' are shared. Lufthansa can't see other airlines.

-- Step 3: Create a recipient and grant access
CREATE RECIPIENT lufthansa WITH SHARING_IDENTIFIER 'lufthansa.databricks.com';
GRANT SELECT ON SHARE airline_bookings_share TO RECIPIENT lufthansa;
-- Lufthansa now has read-only access to their filtered booking data

New December 2025: Delta Sharing now supports Iceberg clients β€” recipients can use Snowflake, Trino, or Flink to read shared Delta data. The recipient doesn't even need Databricks!

SECTION 7: NEW 2026 FEATURES (30 min)

Answer First: If you needed to update 3 tables atomically (all succeed or all fail), you had to write complex error-handling code.

Memory Map: Multi-table Transactions -> coordinated writes stage changes across tables -> transaction boundary commits all participating actions -> failure aborts the grouped operation -> version history proves atomic visibility [DB_03_Azure_Platform_Governance.md:750].

Q17: What are Multi-table Transactions?

Simple Explanation: Before March 2026, Delta Lake transactions were per-table. If you needed to update 3 tables atomically (all succeed or all fail), you had to write complex error-handling code.

Multi-table Transactions (Public Preview March 2026) let you wrap multiple table operations in a single atomic block using BEGIN ATOMIC ... END;. Either ALL operations succeed, or NONE of them apply.

Real-world analogy: Transferring money between bank accounts. You want to debit Account A AND credit Account B atomically. If the credit fails, the debit should also be rolled back. Multi-table transactions = this guarantee for your data tables.

sql
-- Process a new booking: update 3 tables atomically
BEGIN ATOMIC
    -- 1. Insert the booking into the fact table
    INSERT INTO fact_bookings VALUES (...);

    -- 2. Update the passenger's loyalty tier
    UPDATE dim_passenger SET loyalty_tier = 'Gold'
    WHERE passenger_id = 'PAX-123';

    -- 3. Record in the audit log
    INSERT INTO audit_log VALUES (...);
END;
-- If ANY of these 3 operations fails, ALL are rolled back
-- No half-completed bookings in the system!

Requirement: All tables must be Unity Catalog managed tables (not external tables).

Example use case: "When processing a booking, we update the fact table, the passenger dimension, and the audit log atomically. Multi-table transactions guarantee we never have a booking without a corresponding audit entry."

Answer First: Compatibility Mode keeps Delta as the authoritative table while publishing metadata that lets supported non-Delta engines read a compatible Parquet representation. External-reader tests must verify refresh and feature limits.

Memory Map: Compatibility Mode -> Delta remains the authoritative writable table -> compatibility metadata exposes portable Parquet semantics -> external engines consume the published representation -> refresh and reader tests detect divergence [DB_03_Azure_Platform_Governance.md:782].

Q18: What is Compatibility Mode?

Simple Explanation: Delta Lake uses a specific format that only Delta-compatible engines can read. But what if your partner uses Snowflake or AWS Athena (which don't natively support Delta)? Compatibility Mode makes your Delta table readable by non-Delta engines β€” they see it as plain Parquet.

sql
-- Enable compatibility mode
ALTER TABLE bookings SET TBLPROPERTIES ('delta.compatibility.mode' = 'read');
-- Now Athena, Snowflake, Azure Fabric can read this table
-- They see it as regular Parquet files β€” no Delta SDK needed

When to use: When you need to share data with teams/partners who use non-Databricks tools.

Answer First: Lakebase is a managed PostgreSQL-compatible operational database for low-latency transactional application workloads. It complements lakehouse analytics when applications need millisecond row lookups and updates.

Memory Map: Lakebase -> Lakebase supplies managed PostgreSQL semantics -> transactional applications perform low-latency row operations -> Databricks governance connects operational and analytical data -> replication metrics verify freshness [DB_03_Azure_Platform_Governance.md:798].

Q19: What is Lakebase?

Simple Explanation: Delta tables are great for analytics (batch queries, BI). But what about when an application needs to look up a passenger's details in 10 milliseconds? Delta tables can't do that β€” they're designed for scanning large amounts of data, not fast single-row lookups.

Lakebase (GA on Azure March 2026) is a serverless PostgreSQL-compatible database built into Databricks. It fills the gap between analytics (Delta) and applications (need fast lookups).

Think of it as: "A real database inside Databricks for application use cases."

Key features:

  • Scale-to-zero: When nobody is querying, it costs $0 (auto-shuts down)
  • Database branching: Create an instant copy for testing (like git branch for databases!)
  • Instant restore: Go back to any point in time
  • Auto-failover HA: If one server fails, another takes over automatically
sql
-- Create a Lakebase database (uses PostgreSQL syntax!)
CREATE DATABASE travelco_app_db ENGINE = 'LAKEBASE';

-- Create a lookup table (standard PostgreSQL)
CREATE TABLE lookup_airports (
    iata_code VARCHAR(3) PRIMARY KEY,   -- Airport code: BLR, DEL, JFK
    airport_name VARCHAR(100),           -- Full name
    city VARCHAR(50),
    country VARCHAR(50)
);
-- This can serve API requests in milliseconds!

When to use Lakebase vs Delta tables:

Use CaseLakebaseDelta Tables
API backend (fast single-row lookups)βœ… Perfect❌ Too slow for single rows
Batch analytics & BI reporting❌ Not designed for thisβœ… Perfect
Feature serving for ML (low-latency)βœ… Good⚠️ OK for batch features
Application CRUD operationsβœ… Perfect❌ Not designed for this

Answer First: In Day 1, we learned that you need to run OPTIMIZE (compact files), VACUUM (clean up old files), and ANALYZE TABLE (refresh statistics) regularly to keep tables healthy. But scheduling these manually for 500+ tables is tedious.

Memory Map: Predictive Optimization -> service observes eligible managed-table workloads -> platform schedules compaction cleanup and statistics work -> operation history exposes automatic actions -> cost and scan trends validate benefit [DB_03_Azure_Platform_Governance.md:837].

Q20: What is Predictive Optimization?

Simple Explanation: In Day 1, we learned that you need to run OPTIMIZE (compact files), VACUUM (clean up old files), and ANALYZE TABLE (refresh statistics) regularly to keep tables healthy. But scheduling these manually for 500+ tables is tedious.

Predictive Optimization does this AUTOMATICALLY. Databricks watches how your tables are used, learns patterns, and runs these maintenance commands at the right time. No manual scheduling needed.

Real-world analogy: Like a Roomba robot vacuum. Instead of manually vacuuming every room on a schedule, the Roomba learns your floor plan and cleans automatically when needed.

What it does:

  • Auto-OPTIMIZE: Compacts small files into large ones when needed
  • Auto-VACUUM: Cleans up old unused files to free storage
  • Auto-ANALYZE: Refreshes table statistics for better query plans
  • Learns patterns: If a table gets heavy writes at midnight, it optimizes at 2am
  • Enabled by default on all new Unity Catalog managed tables (since 2025)
  • No configuration needed β€” just use managed tables!

Example answer: "For our 500+ Delta tables, Predictive Optimization eliminates manual maintenance scheduling. The platform learns each table's access patterns and optimizes automatically β€” saving our team hours of DevOps work every week."

QUICK REVISION CHECKLIST β€” DAY 3

Test yourself β€” can you answer each in 2-3 minutes?

  • What is Unity Catalog? What are the 6 pillars? Explain the 3-level namespace. (Q1)
  • How do you implement column masking and row-level security? Write the SQL. (Q2)
  • What is data lineage? How does Unity Catalog track it automatically? (Q3)
  • What are Storage Credentials β†’ External Locations β†’ External Tables? (Q4)
  • How do you migrate from Hive Metastore to Unity Catalog? (Q5)
  • What is ABAC? How is it different from RBAC? Write a tag-based policy. (Q6)
  • What is Photon? When does it help? When does it NOT help? (Q7)
  • What is Serverless compute? When to use vs standard clusters? (Q8)
  • Job Cluster vs All-Purpose vs Serverless β€” when to use each? (Q10)
  • Explain the Azure Databricks 3-plane architecture. (Q11)
  • How does Key Vault integration work? Why never hardcode secrets? (Q12)
  • What are Service Principals? Why use them for production? (Q13)
  • How do you handle GDPR "right to be forgotten" in Delta Lake? (Q14)
  • What is Delta Sharing? How would a travel platform share data with airline partners? (Q16)
  • What are Multi-table Transactions? When to use? (Q17)
  • What is Lakebase? When to use vs Delta tables? (Q19)
  • What is Predictive Optimization? (Q20)

Azure Platform & Governance β€” Quick Recall

πŸ—ΊοΈ Memory Map
How to use this file:
  • ⚑ = Must remember (95% chance of being asked)
  • πŸ”‘ = Key concept (core understanding needed)
  • ⚠️ = Common trap (interviewers love to test this)
  • 🧠 = Memory Map (mnemonic/acronym β€” memorize this!)
  • πŸ“ = One-liner (flash-card style β€” cover answer, test yourself)
Reading strategy: Read Memory Maps FIRST β†’ then Direct Questions β†’ then Mid-Level.

🧠 MASTER MEMORY MAP β€” Day 3

🧠 AZURE DATABRICKS PLATFORM = "UPGDS"
AZURE DATABRICKS PLATFORM"UPGDS"
UUnity Catalog (governance, security, lineage)
PPhoton Engine (fast C++ query engine)
GGDPR/Governance (PII masking, right to be forgotten)
DDelta Sharing (share data across organizations)
SServerless (no cluster management, instant start)
UNITY CATALOG HIERARCHY"M-C-S-T" (like McDonald's Chicken Sandwich is Tasty)
MMetastore (one per Azure region)
CCatalog (environment: prod, dev, staging)
SSchema (domain: bookings, flights, passengers)
TTable/View/Function/Model/Volume
AZURE ARCHITECTURE"3 Planes"
Control Plane→Databricks-managed (in Databricks' Azure subscription)
Data Plane→Customer-managed (YOUR Azure subscription — ADLS, VMs)
Web App→Databricks UI (notebooks, SQL editor)

SECTION 1: UNITY CATALOG

🧠 Memory Map: Unity Catalog

🧠 UNITY CATALOG = "Security Guard + Librarian + Receptionist"
UNITY CATALOG"Security Guard + Librarian + Receptionist"
Security Guard→Access control (GRANT/REVOKE)
Librarian→Discovery (search, browse all data assets)
Receptionist→Audit log (who accessed what, when)
6 PILLARS = "ADLAQS" (sounds like "AD-LACS")
AAccess Control (GRANT/REVOKE at any level)
DDiscovery (search + browse all tables)
LLineage (column-level data flow tracking)
AAuditing (every query logged)
QQuality Monitoring (anomaly detection)
SSharing (Delta Sharing protocol)
3-LEVEL NAMESPACE:
catalog.schema.table
travel_prod.bookings.fact_flights
^^^^^^^^^^^ ^^^^^^^^ ^^^^^^^^^^^^
environment domain object

⚑ MUST KNOW DIRECT QUESTIONS

Q1What is Unity Catalog?

Centralized governance solution for all Databricks data assets β€” tables, views, ML models, files, functions. Manages access control, lineage, auditing, and data discovery across all workspaces.

Q2What is the 3-level namespace?

catalog.schema.table β€” Example: travel_prod.bookings.fact_flights. Replaced the old 2-level Hive Metastore (database.table).

Q3What is a Metastore?
βœ… Pro Tip
Top-level container in Unity Catalog β€” one per Azure region. Holds all catalogs. Multiple workspaces can share one metastore.
Q4What objects does Unity Catalog manage?

Tables, Views, Functions, ML Models, Volumes (files like CSVs, images), Connections (external systems), Shares (Delta Sharing).

Q5What is row-level security?

Different users see different ROWS from the same table. Example: Lufthansa team sees only Lufthansa bookings, Air India team sees only Air India bookings.

Q6What is column masking?

Different users see different VALUES for the same column. Example: HR sees full email, analytics team sees kri***@gmail.com.

Q7How do you implement row-level security?

sql
CREATE FUNCTION airline_filter(airline STRING)
  RETURN IF(IS_MEMBER('admin_group'), true, airline = CURRENT_USER_AIRLINE());
ALTER TABLE bookings SET ROW FILTER airline_filter ON (airline_code);

Q8What is data lineage?

Automatic tracking of data flow β€” which table feeds into which table, column by column. Unity Catalog captures this from every query. No manual setup needed.

⚠️ Q9What is the difference between Managed and External tables in Unity Catalog?

Managed TableExternal Table
Data storedUnity Catalog's managed storageYour ADLS path (you control)
DROP TABLEDeletes data + metadataDeletes metadata ONLY
Use whenDefault β€” simplerData shared across platforms (Synapse, etc.)
NeedsNothing extraStorage Credential + External Location

Q10What is a Storage Credential?

Authentication to access external storage (ADLS Gen2). Uses Azure Service Principal or Managed Identity. Created by admin, referenced by External Locations.

Q11What is an External Location?

Maps a Storage Credential to a specific ADLS path. Example: "Service Principal X can access abfss://container@storage.dfs.core.windows.net/path/"

πŸ”‘ MID-LEVEL QUESTIONS

Q12How to migrate from Hive Metastore to Unity Catalog?
πŸ“ Note
4 steps:
  1. Create Unity Catalog metastore + catalogs + schemas
  2. Create External Locations for existing ADLS paths
  3. CREATE TABLE uc_catalog.schema.table CLONE hive_metastore.db.table (DEEP or SHALLOW clone)
  4. Update all notebooks/jobs to use 3-level namespace ⚠️ Don't do big-bang migration β€” migrate one schema at a time, run old + new in parallel
Q13RBAC vs ABAC β€” what's the difference?

RBACABAC
Full nameRole-Based Access ControlAttribute-Based Access Control
How it worksGRANT to groups/rolesGRANT based on tags/attributes
ExampleGRANT SELECT ON bookings TO analystsIf table has tag pii=true, only data_stewards can access
ScaleNeed many GRANT statementsOne policy covers all PII tables automatically
When to useSimple org (<50 tables)Complex org (1000s of tables with varying sensitivity)
ABAC is new (Nov 2025) β€” mention it to show you're up to date!

SECTION 2: PHOTON ENGINE

🧠 Memory Map: Photon

PHOTON"C++ turbo engine for SQL queries"
Think: NORMAL CAR vs SPORTS CAR
Normal (JVM Spark) = Works fine, fuel-efficient
Sports (Photon) = 2-8x faster, costs more (higher DBU rate)
WHEN PHOTON HELPS
βœ“ SQL-heavy workloads (aggregations, joins, GROUP BY)
βœ“ Large table scans (full-table analytics)
βœ“ ETL with heavy transformations
WHEN PHOTON DOESN'T HELP:
βœ— Python UDFs (Photon is C++, can't accelerate Python code)
βœ— ML training (Photon is for SQL, not MLlib)
βœ— Small tables (overhead > benefit)
βœ— Streaming with low latency (Photon optimizes throughput, not latency)
Remember: "Photon = SQL turbo, not Python/ML turbo"

⚑ MUST KNOW DIRECT QUESTIONS

Q14What is Photon?

A C++ vectorized query engine built into Databricks. Runs SQL queries 2-8x faster than standard Spark. Replaces the JVM-based Spark SQL engine for supported operations.

Q15When should you NOT use Photon?

When using Python UDFs, ML training (MLlib), or streaming with latency requirements. Photon only accelerates SQL-style operations (scans, joins, aggregations).

⚠️ Q16Does Photon cost more?

Yes β€” Photon-enabled clusters use a higher DBU rate. But if queries finish 3x faster, total cost may be LOWER. Always benchmark both.

Q17Is Photon enabled by default?

Yes, on Databricks SQL Warehouses and Jobs clusters (since 2024). For interactive clusters, you choose photon-enabled runtime.

SECTION 3: SERVERLESS COMPUTE

🧠 Memory Map: Compute Types

πŸ“ Architecture Diagram
3 COMPUTE TYPES = "JAG" (like Jaguar β€” fast!)
    J β€” Job Clusters (start for a job, auto-terminate after)
    A β€” All-Purpose Clusters (always on, for development)
    S β€” Serverless (no cluster management, instant start)

    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚ COST COMPARISON:                                      β”‚
    β”‚                                                       β”‚
    β”‚   All-Purpose  $$$$  (most expensive β€” always running)β”‚
    β”‚   Job Cluster  $$    (cheaper β€” runs only when needed)β”‚
    β”‚   Serverless   $-$$  (no idle cost, but higher DBU)   β”‚
    β”‚                                                       β”‚
    β”‚   Rule: Development β†’ All-Purpose                     β”‚
    β”‚         Production  β†’ Job Cluster or Serverless       β”‚
    β”‚         SQL queries β†’ Serverless SQL Warehouse        β”‚
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

SERVERLESS WORKSPACES (new Jan 2026):
    = Entire workspace where ALL compute is serverless
    No cluster configuration AT ALL β€” just write code and run

⚑ MUST KNOW DIRECT QUESTIONS

Q18What is Serverless compute?

Databricks manages the infrastructure β€” no cluster configuration, instant start (~10 seconds), auto-scales, auto-terminates. You pay per query/job, no idle cost.

Q19What are the 3 compute types?

  1. All-Purpose Cluster β€” interactive development, always on, most expensive
  2. Job Cluster β€” starts for a job, auto-terminates after, production workloads
  3. Serverless β€” instant start, no config, pay-per-use, newest option

Q20What are Serverless Workspaces?

A workspace where ALL compute is serverless (GA January 2026). No cluster management at all. Engineers just write code and run β€” Databricks handles everything.

⚠️ Q21Job Cluster vs All-Purpose β€” when to use which?

  • All-Purpose: Development, exploration, ad-hoc analysis (need clusters always ready)
  • Job Cluster: Production ETL (spin up β†’ run job β†’ auto-terminate β†’ save money)
  • ⚠️ NEVER use All-Purpose for production β€” wastes money even when idle

SECTION 4: AZURE-SPECIFIC INTEGRATION

🧠 Memory Map: Azure Integration

πŸ“ Architecture Diagram
AZURE DATABRICKS ARCHITECTURE = "3 PLANES"

    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚    CONTROL PLANE                β”‚ ← Databricks' Azure subscription
    β”‚    (Databricks manages this)    β”‚    Notebook server, Web UI,
    β”‚                                 β”‚    Cluster manager, Job scheduler
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                 β”‚ Secure connection
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚    DATA PLANE                   β”‚ ← YOUR Azure subscription
    β”‚    (You manage this)            β”‚    VMs (workers), ADLS Gen2,
    β”‚                                 β”‚    VNET, Key Vault, NSGs
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

KEY AZURE SERVICES:
    ADLS Gen2    β†’ Storage (where Delta tables live)
    Key Vault    β†’ Secrets (passwords, API keys, connection strings)
    Service Principal β†’ Identity for automated jobs (no personal login)
    Azure DevOps β†’ CI/CD pipelines (deploy notebooks, jobs)
    Event Grid   β†’ Auto Loader notification mode trigger
    VNET         β†’ Network isolation (compliance requirement)

Remember: "AKSEV" = ADLS, Key Vault, Service Principal, Event Grid, VNET

⚑ MUST KNOW DIRECT QUESTIONS

Q22What is ADLS Gen2?

Azure Data Lake Storage Gen2 β€” hierarchical cloud storage where all Delta tables, raw files, and landing zones live. Combines blob storage performance with file system semantics.

Q23What is Azure Key Vault?

Secure secret management service. Store database passwords, API keys, storage account keys. Databricks reads secrets at runtime β€” no secrets in code.

python β€” editable
password = dbutils.secrets.get(scope="key-vault-scope", key="oracle-password")

Q24What is a Service Principal?

An Azure identity for applications (not humans). Used for production jobs instead of personal logins. Has its own client ID + secret. Better for automation + auditing.

⚠️ Q25Why use Service Principal instead of personal login for production?

  1. Personal accounts can be disabled when employees leave β†’ jobs break
  2. Service Principals can have minimal permissions (least privilege)
  3. Better audit trail β€” you know exactly which application accessed data
  4. No interactive login required β€” works in CI/CD pipelines

Q26What are the 3 planes of Azure Databricks?
πŸ“ Note
  1. Control Plane (Databricks' subscription) β€” Web UI, notebook service, cluster manager
  2. Data Plane (YOUR subscription) β€” VMs, ADLS storage, VNET
  3. Web Application β€” The Databricks UI you interact with

SECTION 5: GDPR & DATA GOVERNANCE

🧠 Memory Map: GDPR

πŸ“‹ Overview
GDPR"European law: users own their data"
KEY REQUIREMENT: "Right to be Forgotten"
= User requests: "Delete ALL my personal data"
= You MUST locate every governed table, export, and backup
DATABRICKS GOVERNANCE FLOW
Step 1: Use Unity Catalog tags + lineage to locate PII
Step 2: Orchestrate approved deletion across owners and downstream systems
Step 3: Record request, approvals, affected assets, and completion evidence
Step 4: Apply masking or pseudonymization when policy permits retention
PII HANDLING in Unity Catalog:
Tag columns: ALTER TABLE passengers ALTER COLUMN email SET TAGS ('pii' = 'true');
Mask columns: Column masking functions (analysts see masked emails)
Audit: Who accessed PII columns?β†’Unity Catalog audit logs

Delta history, retention, and physical-erasure mechanics are maintained in the canonical Delta right-to-erasure procedure.

⚑ MUST KNOW DIRECT QUESTIONS

Q27What is GDPR "Right to be Forgotten"?

A subject can request erasure of personal data. Unity Catalog tags and lineage support discovery and audit, while physical Delta purge details remain in the canonical Delta procedure.

Delta integration summary β€” πŸ“ Q28: How do you implement Right to be Forgotten in Delta Lake?: Databricks governance orchestrates the subject-request workflow; Delta history, DELETE, VACUUM retention checks, and physical-erasure verification remain in the canonical Delta recovery answer. Open the canonical Delta answer.

Q29What is pseudonymization?

Replace real PII with fake/hashed values. Example: "Krishna Yadav" β†’ "User_A7B3C". Preserves data for analytics while removing identity. Alternative to hard delete.

Q30How do you tag PII columns in Unity Catalog?

sql
ALTER TABLE passengers ALTER COLUMN email SET TAGS ('pii' = 'true');
ALTER TABLE passengers ALTER COLUMN phone SET TAGS ('pii' = 'true');
Tags can be used with ABAC policies to auto-restrict access.

SECTION 6: DELTA SHARING & NEW FEATURES

🧠 Memory Map: Delta Sharing

DELTA SHARING"Share data WITHOUT copying"
Think: Google Docs SHARING (view link β€” they don't download a copy)
Provider: Travel platform (shares booking data)
Recipient: Partner airline (reads shared data)
Share: A named collection of tables to share
How it works:
1. Provider creates a SHARE with specific tables
2. Provider adds RECIPIENT (partner airline)
3. Recipient gets an activation link
4. Recipient reads data using open protocol (Spark, pandas, Power BI)
⚠️Data is NOT copied β€” recipient reads from provider's storage
Remember: "PSR" = Provider, Share, Recipient

⚑ MUST KNOW DIRECT QUESTIONS

Q31What is Delta Sharing?

Open protocol for sharing data across organizations WITHOUT copying. Provider grants access to specific tables, recipient reads live data. Works with Spark, pandas, Power BI.

Q32Why not just copy data to share it?
βœ… Pro Tip
Copying = stale data, storage cost, security risk (multiple copies). Delta Sharing = always fresh, no duplication, centralized access control.
Q33What is Compatibility Mode?

Allows external tools (that only understand Iceberg/Hive) to read your Delta tables. Unity Catalog translates metadata on-the-fly. No table conversion needed.

Q34What are Multi-table Transactions?

sql
BEGIN ATOMIC
  INSERT INTO bookings VALUES (...);
  UPDATE passenger_counts SET count = count + 1;
  INSERT INTO audit_log VALUES (...);
END
Either ALL 3 statements succeed, or NONE do. New in Databricks 2025.

🧠 FINAL REVISION β€” Day 3 Summary Card

πŸ“ Architecture Diagram
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚               DAY 3: PLATFORM & GOVERNANCE                   β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                                             β”‚
β”‚  Unity Catalog = Governance for ALL data assets             β”‚
β”‚  Namespace: catalog.schema.table (3 levels)                 β”‚
β”‚  6 pillars: Access, Discovery, Lineage, Audit, Quality,    β”‚
β”‚             Sharing ("ADLAQS")                              β”‚
β”‚  Row-level security = different users see different rows    β”‚
β”‚  Column masking = different users see masked values         β”‚
β”‚  ABAC (new) = tag-based access control (pii=true β†’ block)  β”‚
β”‚                                                             β”‚
β”‚  Managed Table: DROP deletes data + metadata                β”‚
β”‚  External Table: DROP deletes metadata ONLY                 β”‚
β”‚  External needs: Storage Credential + External Location     β”‚
β”‚                                                             β”‚
β”‚  Photon = C++ SQL engine (2-8x faster)                      β”‚
β”‚  ⚠️ Doesn't help Python UDFs or ML training                 β”‚
β”‚                                                             β”‚
β”‚  Compute: All-Purpose (dev) β†’ Job Cluster (prod) β†’          β”‚
β”‚           Serverless (newest, instant start)                 β”‚
β”‚  ⚠️ NEVER use All-Purpose for production!                   β”‚
β”‚                                                             β”‚
β”‚  Azure: ADLS Gen2 (storage), Key Vault (secrets),           β”‚
β”‚         Service Principal (production identity)             β”‚
β”‚  3 Planes: Control (Databricks) + Data (yours) + Web App   β”‚
β”‚                                                             β”‚
β”‚  GDPR: DELETE + VACUUM 0 HOURS = right to be forgotten     β”‚
β”‚  Alternative: Pseudonymization (hash PII, keep data)        β”‚
β”‚  PII tagging: ALTER COLUMN SET TAGS ('pii' = 'true')       β”‚
β”‚                                                             β”‚
β”‚  Delta Sharing: Share without copying (PSR pattern)         β”‚
β”‚  Multi-table Tx: BEGIN ATOMIC...END (all or nothing)        β”‚
β”‚  Compatibility Mode: Iceberg/Hive clients read Delta        β”‚
β”‚                                                             β”‚
β”‚  TOP 5 THINGS TO SAY IN INTERVIEW:                          β”‚
β”‚  1. "Unity Catalog: single governance for all data assets"  β”‚
β”‚  2. "Row/column security for multi-tenant airline data"     β”‚
β”‚  3. "Service Principals for production, Key Vault for secrets"β”‚
β”‚  4. "GDPR: DELETE + VACUUM 0 HOURS for right to forget"    β”‚
β”‚  5. "Delta Sharing for partner airlines without copying"    β”‚
β”‚                                                             β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
πŸ—ΊοΈ Memory Map
Study tip: Read this file TWICE:
  1. First pass (30 min): Read only 🧠 Memory Maps + ⚑ Direct Questions
  2. Second pass (30 min): Read πŸ”‘ Mid-Level Questions + ⚠️ Traps
  3. Before interview (15 min): Read ONLY the Final Revision Summary Card

TOPIC 3: AZURE PLATFORM & GOVERNANCE (Unity Catalog, Photon, Serverless, ADLS Gen2, GDPR)

L1 β€” Direct / Simple Questions

  1. What is Unity Catalog in Databricks?

    A centralized governance solution for all data and AI assets in Databricks. Provides a single place to manage access control, auditing, lineage, and data discovery across all workspaces. Replaces the legacy Hive Metastore.

2. What is the three-level namespace in Unity Catalog (catalog.schema.table)?

The hierarchical naming convention: catalog.schema.table. Example: travel_prod.bookings.fact_bookings. Catalog = top-level container (like a database server). Schema = logical grouping (like a database). Table = data asset. Enables fine-grained access control at each level.

3. What is a metastore in Unity Catalog?

The top-level container for all Unity Catalog metadata in a region. One metastore per region, shared across all workspaces in that region. Stores catalog definitions, schemas, table metadata, permissions, and audit logs. Not the same as Hive Metastore.

4. What is the difference between managed and external tables in Unity Catalog?

Managed: data stored in the metastore's managed storage location. Dropping the table deletes data. External: data stored at a user-specified location (ADLS Gen2 path). Dropping the table removes metadata but data persists. Use managed for most tables; external when data is shared across platforms.

5. What is a storage credential in Unity Catalog?

An object that stores authentication details for accessing cloud storage (e.g., Azure managed identity, service principal). It authorizes Databricks to read/write to external storage locations. Created once, referenced by external locations.

6. What is an external location in Unity Catalog?

A mapping between a cloud storage path (e.g., abfss://container@account.dfs.core.windows.net/path/) and a storage credential. Defines which storage paths Databricks can access and with which credentials. Required for creating external tables.

7. What is data lineage in Unity Catalog?

πŸ“ Note
Automatic tracking of data flow: which tables read from which tables, which notebooks/jobs produced which tables. Visible in the Unity Catalog UI as a lineage graph. Helps with impact analysis ("if I change this table, what downstream is affected?") and compliance.

8. What is Photon engine in Databricks?

A native C++ vectorized query engine that replaces parts of the JVM-based Spark SQL engine. Accelerates SQL and DataFrame operations 2-5x by using CPU-level optimizations (SIMD, columnar processing). Not all operations benefit β€” Python UDFs bypass Photon.

9. What is Serverless compute in Databricks?

πŸ“ Note
Databricks-managed compute that eliminates cluster management. Instant startup (seconds vs minutes), auto-scales, auto-terminates. Available for SQL Warehouses, notebooks, and jobs. You pay per DBU consumed, not for idle time. Trade-off: slightly higher per-DBU cost but no idle waste.

10. What is ADLS Gen2 and how does Databricks connect to it? > Azure Data Lake Storage Gen2 β€” Azure's scalable object storage with hierarchical namespace. Databricks connects via: (1) Storage credentials (managed identity or service principal), (2) abfss:// protocol for access, (3) Unity Catalog external locations for governance. It's the primary storage layer for Lakehouse data on Azure.

11. What is the difference between a Databricks workspace and a metastore? > Workspace: an environment where users run notebooks, jobs, and SQL queries. Each workspace has its own URL and users. Metastore: the centralized metadata store shared across multiple workspaces in a region. One metastore can serve many workspaces, enabling cross-workspace data sharing and governance.

12. What is row-level security in Unity Catalog? > Restricting which rows a user can see based on their identity. Implemented via dynamic views: CREATE VIEW secure_bookings AS SELECT * FROM bookings WHERE airline_id = current_user_airline(). Different users see different rows from the same table.

13. What is column masking in Unity Catalog? > Hiding or redacting sensitive column values based on user permissions. Example: CREATE FUNCTION mask_email(email STRING) RETURNS STRING RETURN CASE WHEN is_member('pii_readers') THEN email ELSE '***@***' END. Applied to columns via ALTER TABLE ... ALTER COLUMN ... SET MASK mask_email.

14. What is a service principal in Databricks on Azure? > A non-human identity (robot account) used for automated processes. Production jobs run as service principals (not personal accounts) for security and accountability. Created in Azure AD, registered in Databricks, granted permissions via Unity Catalog.

15. What is the difference between instance profiles and storage credentials? > Instance profiles (AWS concept) are IAM roles attached to clusters. Storage credentials (Unity Catalog concept) are authentication objects for cloud storage access. On Azure, storage credentials use managed identities or service principals. Storage credentials are the Unity Catalog way; instance profiles are legacy.

16. What are tags and labels in Unity Catalog for data classification? > Key-value metadata attached to tables and columns for classification. Example: ALTER TABLE SET TAGS ('pii' = 'true', 'classification' = 'confidential'). Used for data discovery, compliance (find all PII tables), and ABAC policies (attribute-based access control) where access is granted based on tags.

17. What is the system tables feature in Unity Catalog? > Built-in tables that expose operational metadata: system.billing.usage (cost per job/cluster), system.access.audit (who accessed what), system.compute.clusters (cluster activity). Enables cost monitoring, security auditing, and usage analytics without external tools.

18. What is Databricks SQL (DBSQL)? > A SQL-native analytics experience in Databricks. Provides SQL editor, dashboards, alerts, and SQL Warehouses (optimized compute for SQL queries). Used by BI analysts and connected to tools like Power BI, Tableau. Powered by Photon for fast query execution.

19. What is a SQL Warehouse (Serverless vs Pro vs Classic)? > Compute endpoints optimized for SQL queries. Serverless: instant startup, auto-scale, auto-suspend (best for variable workloads). Pro: Photon-powered, predictable pricing (best for steady heavy SQL). Classic: legacy, cheaper but slower startup. All provide JDBC/ODBC endpoints for BI tools.

20. What is GDPR and what does it mean for data engineering? > EU regulation requiring: right to access (show what data you hold), right to deletion (erase personal data on request), data minimization (collect only what's needed), breach notification (report breaches within 72 hours). For data engineering: implement deletion pipelines across all layers, pseudonymize PII, maintain audit logs, enforce column masking.

L2

  1. Explain the Unity Catalog hierarchy: metastore -> catalog -> schema -> table/view/function. How do permissions cascade?

    Key points: Permissions cascade downward: GRANT SELECT ON CATALOG travel_prod gives read access to ALL schemas and tables within. Can be overridden at lower levels with DENY. Best practice: grant at the most restrictive level needed. Admin permissions: metastore admin β†’ catalog owner β†’ schema owner β†’ table owner. Each level can manage its own children.

2. Compare Unity Catalog vs legacy Hive Metastore β€” what are the key differences and migration challenges?

Key points: UC provides centralized governance across workspaces, fine-grained access control, lineage, and data sharing β€” Hive MS provides none of these. Migration challenges: (1) external tables need storage credentials and external locations, (2) existing GRANT statements need conversion to UC syntax, (3) db.table becomes catalog.schema.table, (4) dbutils.fs.mount replaced by external locations. Use UCX migration tool for automated assessment and migration.

3. How does Photon engine accelerate queries? What workloads benefit most from Photon?

Key points: Photon uses native C++ code instead of JVM, vectorized processing (operates on batches of rows, not one at a time), SIMD CPU instructions, and columnar memory layout. Benefits most: SQL-heavy workloads, scans, aggregations, joins, MERGE operations. Benefits least: Python UDFs (bypass Photon), ML training (not SQL-based), simple read-only queries on small tables. Typically 2-5x speedup on eligible operations.

4. Compare Serverless SQL Warehouses vs Classic SQL Warehouses β€” cost, startup time, scaling.

Key points: Serverless: <10 sec startup, auto-scale 0 to N, auto-suspend, higher per-DBU cost but zero idle cost. Classic: 5-10 min startup, manual scaling configuration, lower per-DBU cost but idle cost adds up. Serverless wins for: variable workloads, evening/weekend BI usage, demo environments. Classic wins for: steady 24/7 SQL workloads where utilization is consistently high.

5. Explain how ADLS Gen2 integrates with Databricks β€” authentication methods (OAuth, service principals, access keys).

Key points: (1) Storage credentials with managed identity (recommended β€” no secrets to manage), (2) Service principal with client ID/secret (good for cross-tenant access), (3) Access keys (legacy β€” full account access, not granular). Unity Catalog approach: create storage credential β†’ create external location β†’ reference in table definitions. Avoid mounting (dbutils.fs.mount) β€” it's legacy and bypasses UC governance.

6. How do you implement GDPR "Right to be Forgotten" in a Lakehouse architecture?

Unity Catalog tags and lineage locate governed copies, while the platform workflow records approvals, downstream exports, backups, and completion evidence. Delta owns physical erasure and retention safety; see the canonical right-to-erasure answer.

7. Explain dynamic views in Unity Catalog for row-level and column-level security.

Key points: Dynamic views use current_user(), is_member(), and is_account_group_member() functions to return different data per user. Row-level: WHERE region = user_region(). Column-level: CASE WHEN is_member('pii_readers') THEN ssn ELSE 'XXXX' END. Advantage over static views: one view serves all users with appropriate filtering. Applied via CREATE VIEW with security functions.

8. How does Unity Catalog handle cross-workspace data sharing?

Key points: All workspaces in a region share the same UC metastore. A table created in workspace A is immediately accessible in workspace B (subject to permissions). No data copying needed. Permissions are managed centrally in UC β€” GRANT SELECT applies across all workspaces. For cross-region or cross-account sharing, use Delta Sharing.

9. Compare Azure Databricks vs Azure Synapse Analytics β€” when would you recommend each?

Key points: Databricks: best for Lakehouse architecture, ML/AI, streaming, complex ETL, open formats (Delta). Synapse: best for traditional data warehousing (dedicated SQL pools), tight Azure integration (Power BI, Azure AD), serverless SQL on files. Recommend Databricks when: unified BI + ML platform, multi-cloud flexibility, Delta Lake ecosystem. Recommend Synapse when: existing SQL Server shop, simple warehouse workloads, Synapse Pipelines preferred over Airflow.

10. Explain how audit logging works in Unity Catalog. What events are captured? > Key points: UC automatically logs: table reads/writes, permission changes, login events, query execution, data exports. Stored in system.access.audit system table. Queryable via SQL. Events include: who, when, what action, which resource, from which workspace. Integrate with Azure Monitor or Sentinel for SIEM. Retention: configurable, typically 90+ days for compliance.

11. How do you implement data classification (PII tagging) using Unity Catalog? > Key points: (1) Manual: ALTER TABLE ... ALTER COLUMN email SET TAGS ('pii' = 'true'), (2) Automated: scan tables with regex patterns (email, phone, SSN) using a classification notebook, (3) Use UC tags + ABAC: create policies that reference tags (GRANT SELECT ON TABLES WITH TAG 'pii' = 'false'). (4) New (2025): Databricks auto-classification feature scans and tags PII columns automatically. Tag at column level, not just table level.

12. What are the networking options for Databricks on Azure (VNet injection, Private Link, NSGs)? > Key points: VNet injection: Databricks control plane deploys into your Azure VNet β€” clusters get your VNet IPs. Private Link: traffic between Databricks control plane and your VNet stays on Microsoft's backbone (no public internet). NSGs: Network Security Groups control inbound/outbound traffic for cluster subnets. Best practice: VNet injection + Private Link + NSGs for zero-public-internet architecture. Required for financial services and healthcare.

13. Explain the difference between account-level and workspace-level identity in Databricks. > Key points: Account-level: users and groups defined in the Databricks account console, shared across all workspaces. Workspace-level: legacy model where users are defined per workspace. Unity Catalog requires account-level identities. Migration: sync workspace users to account level, assign to workspaces. Best practice: manage all identities at account level via SCIM (automated provisioning from Azure AD).

14. How does Unity Catalog system tables help with cost monitoring and query auditing? > Key points: system.billing.usage: DBU consumption per workspace, cluster, job β€” enables chargeback. system.access.audit: who ran which query, when, on which table β€” enables security auditing. system.compute.clusters: cluster start/stop times, utilization β€” find idle clusters. Build dashboards: "Top 10 most expensive jobs this month", "Who accessed PII tables last week", "Clusters running > 8 hours with < 20% utilization".

15. Compare managed identity vs service principal vs access key for ADLS Gen2 access β€” pros/cons. > Key points: Managed identity: no secrets to manage, auto-rotated by Azure, most secure, works only within Azure. Service principal: secret-based, works cross-tenant, must rotate manually, good for cross-cloud. Access key: full account-level access (no granularity), shared secret, least secure, easiest setup. Recommendation: managed identity for single-tenant Azure, service principal for cross-tenant, never use access keys in production.

16. Explain how Databricks handles encryption at rest and in transit on Azure. > Key points: At rest: ADLS Gen2 encrypts with Microsoft-managed keys (default) or customer-managed keys (CMK) in Azure Key Vault. Cluster local disks encrypted by Azure. In transit: TLS 1.2 between all components (control plane, data plane, storage). Notebook content encrypted in control plane storage. For compliance: enable CMK for all storage, verify TLS is enforced, audit encryption status.

17. How do you design a multi-region Databricks deployment on Azure? > Key points: One UC metastore per region. Use Delta Sharing for cross-region data access. Active-passive: primary region for writes, secondary for DR reads. Data replication: ADLS Gen2 geo-replication for storage, replicate Delta tables via DEEP CLONE. Considerations: data sovereignty (some data cannot leave its region), latency (cross-region reads are slower), cost (replication storage cost).

18. What is the role of Azure Key Vault in Databricks? How do you manage secrets? > Key points: Key Vault stores secrets (passwords, API keys, connection strings). Databricks accesses via secret scopes backed by Key Vault. Access in code: dbutils.secrets.get(scope="kv-scope", key="db-password"). Value is REDACTED in notebook output (even with print()). Best practices: one Key Vault per environment, least-privilege access, enable soft-delete, rotate secrets regularly.

19. Explain the difference between GRANT, DENY, and REVOKE in Unity Catalog's permission model. > Key points: GRANT: give a permission (additive). DENY: explicitly block a permission (overrides GRANT β€” takes precedence). REVOKE: remove a previously granted or denied permission (return to inherited state). Example: GRANT SELECT ON CATALOG gives read access. DENY SELECT ON specific table overrides the catalog-level GRANT for that table only. REVOKE removes the explicit DENY, reverting to the catalog-level GRANT.

20. How does Unity Catalog's data lineage differ from tools like Apache Atlas or Purview? > Key points: UC lineage: automatic (no instrumentation needed), table-to-table and column-to-column lineage, captured from Spark jobs and SQL queries, visible in UC UI. Atlas/Purview: require explicit instrumentation or connectors, broader scope (non-Databricks systems), more metadata types (business glossary, classification). UC lineage is best for Databricks-internal lineage. Purview is best for organization-wide data governance across Azure services.

L3 β€” Scenario-Based Questions

  1. Unity Catalog Migration: Your organization has 500 tables in Hive Metastore across 3 workspaces. Design a migration plan to Unity Catalog with zero downtime.

    ⚠️ Common Trap
    Approach: (1) Assess: use UCX tool to inventory all tables, permissions, mounts, (2) Plan: create UC metastore, catalogs, schemas, external locations, (3) Migrate: CREATE TABLE ... AS SELECT * FROM hive_metastore.db.table (or use UCX automated migration), (4) Validate: compare row counts, schemas, permissions, (5) Cutover: update notebooks/jobs to use UC table references, (6) Decommission: disable Hive Metastore access. Key decisions: Run Hive MS and UC in parallel during migration. Migrate dev first, then staging, then prod. Traps: External tables need storage credentials + external locations configured BEFORE migration. Don't forget to migrate permissions.
  2. GDPR Compliance Pipeline: A travel platform handles passenger PII (names, passport numbers, emails) across 100 countries. Design a GDPR-compliant data architecture using Unity Catalog, column masking, and deletion pipelines.

    ⚠️ Common Trap
    Approach: (1) Classify: tag all PII columns with UC tags, (2) Protect: column masking on PII (mask for non-authorized users), row-level security per country/airline, (3) Delete: automated pipeline β€” receive deletion request β†’ find all tables with user's data (via lineage) β†’ DELETE across all layers β†’ VACUUM β†’ log to audit, (4) Minimize: store raw PII only in Bronze, pseudonymize at Silver for analytics. Traps: Must delete from ALL layers including backups. VACUUM 0 hours is needed to physically remove data. Don't forget Delta CDF β€” deleted records might be captured there.
  3. Multi-Team Governance: You have Data Engineering, Data Science, and BI teams sharing a single Databricks deployment. Design the Unity Catalog structure (catalogs, schemas, permissions) for proper isolation and collaboration.

    ⚠️ Common Trap
    Approach: Catalogs: prod (shared read), dev_engineering, dev_datascience, sandbox. Schemas per domain: bookings, passengers, ml_features. Permissions: Engineering owns prod writes. Data Science has read on prod, write on dev_datascience and ml_features. BI has read on Gold schemas only. Key decisions: Sandbox catalog auto-cleaned every 30 days. Service principals for prod writes β€” no human writes to prod. Traps: Don't give everyone access to everything β€” start restrictive, grant on request.
  4. Cost Optimization: Your Azure Databricks bill is $150K/month. 60% is compute. Design a cost reduction strategy using Serverless, autoscaling, spot instances, and cluster policies.

    ⚠️ Common Trap
    Approach: (1) Analyze: system tables β†’ cost per job, team, cluster type, (2) Quick wins: terminate idle clusters ($15K savings), switch prod from All-Purpose to Job Clusters ($25K), enforce auto-termination policies, (3) Medium-term: Spot VMs for batch workers (60% savings on worker cost), right-size oversized clusters, (4) Strategic: Serverless for bursty workloads, Instance Pools for fast startup. Target: 40% reduction = $60K/month savings. Traps: Don't cut compute for SLA-critical jobs β€” optimize, don't degrade.
  5. Secure External Sharing: A partner airline needs read access to specific Gold tables but must NOT see PII columns. Design this using Unity Catalog, Delta Sharing, and dynamic views.

    ⚠️ Common Trap
    Approach: (1) Create dynamic view: CREATE VIEW partner_bookings AS SELECT booking_id, route, fare, '***' AS passenger_name FROM gold_bookings WHERE airline_id = 'PARTNER_A', (2) Create Delta Share: CREATE SHARE partner_a_share, (3) Add view to share: ALTER SHARE ADD TABLE partner_bookings, (4) Create recipient: CREATE RECIPIENT partner_a, (5) Partner reads via Delta Sharing client. Traps: Share views, not base tables (views enforce PII filtering). Audit partner access via system tables.
  6. Photon Decision: Your team is deciding whether to enable Photon on all clusters. Some workloads are Python UDF-heavy, others are SQL-heavy. How do you evaluate and decide?

    ⚠️ Common Trap
    Approach: (1) Benchmark: run representative workloads with and without Photon, measure DBU cost and wall-clock time, (2) SQL-heavy joins/aggregations β†’ likely 2-5x speedup β†’ enable Photon, (3) Python UDF-heavy β†’ Photon can't accelerate UDFs β†’ marginal benefit, higher DBU rate β†’ may not be cost-effective, (4) MERGE-heavy β†’ Photon significantly accelerates MERGE β†’ enable. Key decisions: Enable Photon per cluster type, not globally. SQL Warehouses always use Photon. Traps: Photon has a higher DBU rate β€” if the speedup doesn't offset the cost, it's a net loss.
  7. Network Security: Your security team requires all Databricks traffic to stay within the Azure virtual network and never traverse the public internet. Design the network architecture.

    ⚠️ Common Trap
    Approach: (1) VNet injection: deploy Databricks control plane into your VNet, (2) Private Link: connect workspace to control plane via private endpoints (no public IPs), (3) NSGs: restrict outbound traffic to required Azure services only, (4) Private endpoints for ADLS Gen2, Key Vault, Event Hubs, (5) Disable public access to the workspace URL. Key decisions: Use Azure Firewall for centralized egress control. Configure DNS for private endpoint resolution. Traps: Must allow outbound to Databricks control plane IPs. Must allow NTP, package managers for cluster init.
  8. Disaster Recovery: Design a DR strategy for Databricks on Azure. RTO = 4 hours, RPO = 1 hour. Consider metastore, Delta tables, notebooks, and cluster configurations.

    ⚠️ Common Trap
    Approach: (1) Data: ADLS Gen2 geo-replication (RPO < 1 hour), (2) Metastore: UC metastore is regional β€” create mirror metastore in DR region with automated sync, (3) Notebooks: Git-backed (already replicated), (4) Cluster configs: DABs in Git (deploy to DR with one command), (5) Jobs: DABs recreate all workflows in DR workspace. Failover: Deploy DABs to DR workspace β†’ point pipelines to DR storage β†’ validate β†’ resume operations. Traps: Delta transaction log must be consistent in DR region β€” verify with DESCRIBE HISTORY after failover.
  9. Audit & Compliance: The compliance team needs a report showing who accessed PII data in the last 90 days, what queries they ran, and what data they exported. Design this using system tables.

    ⚠️ Common Trap
    Approach: Query system.access.audit for table access events on PII-tagged tables. Join with UC tags to identify PII tables. Filter by action type: SELECT, COPY, DOWNLOAD. Build a Databricks SQL dashboard with: (1) PII access by user (last 90 days), (2) Most accessed PII tables, (3) Export events (data leaving the platform), (4) Unusual access patterns (access outside business hours). Traps: System tables have retention limits β€” ensure 90+ days configured. Some export paths (JDBC, dbutils.fs.cp) may not be fully audited.
  10. ADLS Gen2 Organization: You have 50 data products across 5 business domains. Design the ADLS Gen2 storage layout (containers, folders) and the corresponding Unity Catalog structure.

    ⚠️ Common Trap
    Approach: Storage: one container per domain (bookings, flights, passengers, payments, analytics). Folders per layer: domain/bronze/entity/, domain/silver/entity/, domain/gold/entity/. UC structure: one catalog per environment (prod, dev, staging), one schema per domain (bookings, flights), tables within schemas. Key decisions: Managed tables for most (UC manages storage). External tables only when data is shared outside Databricks. Traps: Don't create one container per table (too many containers). Don't create one catalog per domain (complicates cross-domain access).
  11. Data Mesh on Databricks: Leadership wants to adopt a Data Mesh approach. How would you structure Unity Catalog catalogs, schemas, and ownership to enable domain-oriented data products?

    ⚠️ Common Trap
    Approach: One catalog per domain (catalog_bookings, catalog_flights). Each domain team owns their catalog. Schemas: bronze, silver, gold within each catalog. Cross-domain access via GRANT SELECT on Gold schemas. Data product discoverability via UC tags and descriptions. Key decisions: Central platform team manages UC metastore, storage credentials, and cluster policies. Domain teams manage their own catalogs, tables, and quality. Traps: Pure data mesh requires domain teams to own their infrastructure β€” may not scale if teams lack data engineering expertise.
  12. Cross-Cloud Access: A team in AWS needs to read data from your Azure Databricks Lakehouse. Design the architecture using Delta Sharing.

    ⚠️ Common Trap
    Approach: (1) Create Delta Share in Azure workspace, (2) Add tables/views to share, (3) Create recipient for the AWS team, (4) AWS team reads via Delta Sharing connector (Spark, pandas, or Databricks on AWS). Data stays in Azure β€” no copying. Network: Delta Sharing uses HTTPS β€” works cross-cloud without VPN. Key decisions: Share views (not base tables) for security filtering. Set up audit logging on share access. Traps: Latency for cross-cloud reads is higher. For high-throughput, consider replicating data to AWS.
  13. PII Detection & Tagging: You inherit 2,000 tables with no documentation. Design an automated PII detection and tagging pipeline using Unity Catalog tags and Databricks notebooks.

    ⚠️ Common Trap
    Approach: (1) Scan: iterate over all tables, sample 1000 rows per column, (2) Detect: regex patterns (email, phone, SSN, passport), statistical patterns (high cardinality string columns), naming conventions (columns named *email*, *phone*, *ssn*), (3) Tag: ALTER TABLE ... ALTER COLUMN ... SET TAGS ('pii' = 'true', 'pii_type' = 'email'), (4) Review: human review of detected PII before tagging, (5) Automate: run weekly to catch new tables. Traps: False positives (order IDs that look like phone numbers). Always human-review before applying tags.
  14. Serverless Migration: Your team runs 200 interactive clusters daily. The CFO wants to move to Serverless. What is your evaluation and migration plan?

    ⚠️ Common Trap
    Approach: (1) Analyze: current cost per cluster, utilization rates, idle time, (2) Pilot: migrate 20 low-risk clusters to Serverless, compare cost and performance, (3) Calculate breakeven: Serverless DBU rate is higher, but zero idle cost β€” calculate crossover point, (4) Migrate: interactive notebooks β†’ Serverless first (biggest idle time savings), then evaluate jobs. Key decisions: Clusters with >50% idle time benefit most from Serverless. 24/7 streaming jobs may be cheaper on provisioned. Traps: Some libraries/init scripts may not work on Serverless. Test compatibility before migrating.
  15. Regulatory Audit: A regulator asks you to prove data lineage from source (Oracle) to final report (Power BI). How do you use Unity Catalog lineage to demonstrate this end-to-end?

    ⚠️ Common Trap
    Approach: (1) UC lineage: shows Delta table-to-table lineage (Bronze β†’ Silver β†’ Gold), (2) Gap: UC doesn't track Oracle source or Power BI destination natively, (3) Fill gaps: document Oracle β†’ Bronze connection in UC table descriptions/tags, document Gold β†’ Power BI in dashboard metadata, (4) Combine: UC lineage (middle) + manual documentation (edges) = end-to-end proof. Traps: UC lineage is only as complete as the workloads that run through Databricks. External sources and destinations need manual documentation or integration with Purview.
Advanced

Databricks Production, CI/CD, and Cost

#

Databricks Production, CI/CD, and Cost

Answer First: Production Databricks work joins deployable configuration, automated tests, least-privilege secrets, retry-safe workflows, observability, and cost attribution.

Memory Map: source -> test -> bundle -> deploy -> run -> observe -> optimize cost.

Delta ownership: This module keeps Databricks integration context concise. Transaction-log, MERGE, time-travel, CDF, and maintenance internals are canonical on /learn/delta.

Performance Tuning & Production Systems

πŸ’‘ Interview Tip
Focus: Spark UI debugging, Unity Catalog, Photon, CI/CD, cost management, governance Approach: Every topic starts with simple explanation + analogy, then interview-level depth

MEMORY MAP: PERFORMANCE TUNING = SPARK-FIX

🧠 PERFORMANCE TUNING β†’ SPARK-FIX
PERFORMANCE TUNINGSPARK-FIX
SShuffle (the #1 performance killer)
PPartitioning (too many β†’ small files, too few β†’ OOM)
AAQE (Adaptive Query Execution β€” auto-fix at runtime)
RResource tuning (executor memory, cores, instances)
KKey skew (one key has 90% of data)
FFile format (Parquet + snappy, avoid CSV/JSON)
IIndexing (Z-ORDER, Liquid Clustering, data skipping)
XeXecution plan (EXPLAIN, Spark UI reading)
Memorize this. When an interviewer asks "how do you tune Spark?",
walk through each letter. It shows a structured mental model.

SECTION 1: SPARK UI DEBUGGING & EXECUTION PLANS

Answer First: An execution plan is Spark's "recipe" for running your query. It tells you exactly what Spark will do β€” which tables to scan, how to join them, where to shuffle data.

Memory Map: you read a Spark execution plan? Walk through a real example -> parser reveals logical transformations -> optimizer exposes Catalyst rewrites -> physical tree identifies exchanges and join operators -> runtime statistics locate actual bottlenecks [05_Performance_Tuning_and_Production.md:30].

Q1: How do you read a Spark execution plan? Walk through a real example.

Simple Explanation: An execution plan is Spark's "recipe" for running your query. It tells you exactly what Spark will do β€” which tables to scan, how to join them, where to shuffle data. Reading a plan is like reading a recipe backward: you start at the bottom (raw ingredients) and read up to the final dish.

Analogy: Think of a GPS route. Before you drive, the GPS shows you the full route β€” highways, turns, toll roads. An execution plan is Spark's GPS route for your query. You read it to spot "toll roads" (shuffles) and "traffic jams" (skew) before the query even runs.

Technical depth:

python β€” editable
df = spark.table("orders") \
    .filter(col("date") == "2025-01-15") \
    .join(spark.table("customers"), "customer_id") \
    .groupBy("region").agg(sum("amount"))

df.explain(True)  # ← True = show all plan levels (parsed, analyzed, optimized, physical)

Reading the plan (BOTTOM UP β€” always start at the bottom):

== Physical Plan ==
*(3) HashAggregate(keys=[region], functions=[sum(amount)]) ← Final aggregation (top = last step)
+- Exchange hashpartitioning(region, 200) ← SHUFFLE (stage boundary β€” expensive!)
+- *(2) HashAggregate(keys=[region], functions=[partial_sum(amount)]) ← Partial agg (map-side combine β€” good!)
+- *(2) Project [region, amount] ← Column pruning (only needed columns)
+- *(2) BroadcastHashJoin [customer_id], [customer_id] ← JOIN (broadcast = no shuffle = fast!)
:- *(2) Filter (date = 2025-01-15) ← Predicate pushdown (filter before join)
: +- *(2) FileScan delta [customer_id, amount, date] ← Table scan (bottom = first step)
: PushedFilters: [EqualTo(date, 2025-01-15)] ← PREDICATE PUSHDOWN working βœ“
+- BroadcastExchange ← Customer table sent to all executors
+- *(1) FileScan delta [customer_id, region] ← Customer table scan (small table)

EXPLAIN plan reading guide β€” how to spot problems:

πŸ—‚οΈSTEP-BY-STEP: Reading any EXPLAIN output
1. START AT THE BOTTOM β€” that's where data enters
2. LOOK FOR "Exchange" β€” every Exchange = a SHUFFLE = a stage boundary
More shuffles = slower query
3. CHECK JOIN TYPE:
BroadcastHashJoin β†’ GOOD (small table copied everywhere, no shuffle)
SortMergeJoin β†’ OKAY but check if broadcast is possible
CartesianProduct β†’ BAD (every row matched with every other row)
4. LOOK FOR "PushedFilters" β†’ means filter is pushed to storage level (good!)
If missing β†’ filter happens AFTER reading all data (bad!)
5. CHECK FOR "PartitionFilters" β†’ means partition pruning is working (good!)
6. LOOK FOR (*) prefix β†’ means WholeStageCodegen is active (good!)
If missing β†’ Spark can't generate optimized code for that operator
7. LOOK FOR "partial_sum" or "partial_count" β†’ map-side combine (reduces shuffle data)

Key things to look for in any plan:

ElementMeaningGood or Bad?
ExchangeSHUFFLE β€” stage boundary, expensiveWatch out
BroadcastHashJoinSmall table broadcast β€” no shuffleGood
SortMergeJoinBoth sides shuffled β€” check if broadcast possibleCheck
PushedFiltersPredicate pushdown is workingGood
FileScan ... PartitionFiltersPartition pruning is workingGood
WholeStageCodegen (*)Codegen active β€” JIT compiledGood
HashAggregate (partial_sum)Map-side combine β€” reduces shuffle dataGood
CartesianProductCross join β€” usually a bugBad

Interview Tip: When asked about execution plans, say: "I always read bottom-up. I look for three things: unnecessary shuffles, join strategies, and whether predicate pushdown is working. Then I check the Spark UI SQL tab to compare estimated vs actual row counts."

What NOT to Say: "I just look at the plan to see if it looks right." That is vague. Always name specific operators you look for.

Answer First: The Spark UI is your dashboard for understanding what Spark is actually doing. When a job is slow, the Spark UI tells you exactly WHERE the bottleneck is β€” which stage, which task, and why.

Memory Map: the key metrics to check in Spark UI for performance issues -> job timeline locates the longest stage -> task distribution reveals skew or stragglers -> exchange and spill counters expose data movement -> executor metrics separate CPU memory and I O pressure [05_Performance_Tuning_and_Production.md:100].

Q2: What are the key metrics to check in Spark UI for performance issues?

Simple Explanation: The Spark UI is your dashboard for understanding what Spark is actually doing. When a job is slow, the Spark UI tells you exactly WHERE the bottleneck is β€” which stage, which task, and why.

Analogy β€” Debugging = Diagnosing a Patient:

πŸ“‹ Overview
Debugging a slow Spark job is like diagnosing a patient:
Step 1: SYMPTOMS→Spark UI Jobs tab: which job is slow?
(Patient says "my chest hurts")
Step 2: VITALS→Stages tab: task duration distribution
1 task 100x slower than median = DATA SKEW
(Blood pressure, heart rate β€” find anomalies)
Step 3: DIAGNOSE→Tasks tab: shuffle size, GC time, spill
Too much shuffle = too much data movement
(Run blood tests β€” identify the root cause)
Step 4: PRESCRIBE→Fix: salt key, broadcast join, repartition, AQE
(Prescribe medication based on diagnosis)

Technical depth β€” the complete debugging checklist:

πŸ—‚οΈSpark UI Debugging Checklist:
1. JOBS TAB:
Which job is slow? (match to your code) # ← Start here always
2. STAGES TAB:
Sort by Duration β€” find the bottleneck stage # ← The slowest stage is your target
Shuffle Read/Write Size β€” large = expensive # ← >1 GB shuffle per stage = investigate
Check Input/Output sizes β€” data amplification? # ← Output >> Input means a bad join
Spill (Memory/Disk) β€” if > 0, memory pressure # ← Spill = data doesn't fit in memory
3. TASKS TAB (within slow stage):
Sort by Duration β€” look for outliers # ← 1 task at 40min, rest at 30sec = SKEW
Max Task Duration vs Median β€” if max >> median = DATA SKEW
GC Time β€” if > 10% of task time = memory pressure # ← Increase executor memory
Shuffle Read Size per task β€” should be roughly equal # ← Unequal = skewed partition
Locality Level β€” PROCESS_LOCAL is best # ← ANY = data is far away
4. SQL TAB:
Check physical plan β€” join strategies correct? # ← SortMerge when Broadcast possible?
Rows output at each node β€” data amplification? # ← Exploding join?
Time spent per operator # ← Which operator is the bottleneck?
5. STORAGE TAB:
Cached RDDs β€” are they persisted as expected? # ← Missing cache = recomputation
6. EXECUTORS TAB:
Active/Dead executors # ← Dead executor = lost work + retry
Memory usage # ← Near max = risk of OOM
Task counts β€” balanced distribution? # ← Unbalanced = skew

Interview Tip: Say: "My debugging workflow has three steps. First, I identify the slowest stage. Second, I check task-level metrics for skew or spill. Third, I look at the SQL tab to verify join strategies and row counts at each operator."

What NOT to Say: "I check if the job succeeded or failed." That is surface-level. Interviewers want to hear about stage-level and task-level debugging.

Answer First: This is the classic data skew problem. One partition has massively more data than the others, so one task takes forever while the rest finish quickly. The entire stage waits for that one slow task.

Memory Map: Scenario β€” You see a stage with 200 tasks, 199 complete in 30 seconds, 1 takes 40 minutes. Diagnose and fix -> single long task isolates skewed input -> key and spill metrics identify dominant partition -> salting or adaptive skew handling redistributes work -> task-duration spread confirms repair [05_Performance_Tuning_and_Production.md:164].

Q3: Scenario β€” You see a stage with 200 tasks, 199 complete in 30 seconds, 1 takes 40 minutes. Diagnose and fix.

Simple Explanation: This is the classic data skew problem. One partition has massively more data than the others, so one task takes forever while the rest finish quickly. The entire stage waits for that one slow task.

Analogy β€” The Supermarket Checkout:

Imagine a supermarket with 200 checkout lanes.
199 lanes have 1 customer each→done in 30 seconds.
1 lane has 10,000 customers→takes 40 minutes.
The store can't close until ALL lanes are done.
That one lane = one partition with a "hot key"
The 10,000 customers = millions of rows for one key value
The store waiting = your Spark stage waiting for one task

Step 1: Confirm skew in Spark UI

  • Spark UI -> Stage detail -> Tasks tab
  • Sort by Shuffle Read Size: the slow task reads 50 GB while others read 100 MB
  • Or sort by Duration: one task at 40 min, median at 30 sec

Step 2: Identify the skewed key

python β€” editable
# Find hot keys β€” which key value has way more rows than others?
df.groupBy("join_key").count().orderBy(col("count").desc()).show(20)

# Example output:
# +----------+----------+
# | join_key |    count |
# +----------+----------+
# | NULL     | 50000000 |  ← THIS IS THE PROBLEM (50M rows for one key!)
# | key_001  |      100 |
# | key_002  |       95 |
# +----------+----------+

Step 3: Fix β€” data skew with salting (showing actual before/after)

python β€” editable
# === BEFORE SALTING ===
# All 50M rows with key=NULL go to ONE partition β†’ ONE task β†’ 40 minutes
#
# Partition 0: [NULL, NULL, NULL, ... 50M rows]  ← OVERLOADED
# Partition 1: [key_001: 100 rows]               ← idle
# Partition 2: [key_002: 95 rows]                ← idle

# === SALTING FIX ===
# Step A: Add a random "salt" (0-9) to the skewed key on the LEFT side
from pyspark.sql.functions import concat, lit, floor, rand

salt_buckets = 10  # ← Split the hot key into 10 sub-keys
large_df_salted = large_df.withColumn(
    "salted_key",
    concat(col("join_key"), lit("_"), floor(rand() * salt_buckets).cast("int"))
)
# Now NULL becomes: NULL_0, NULL_1, NULL_2, ... NULL_9
# 50M rows split across 10 keys = ~5M per partition

# Step B: Explode the RIGHT side to match all salt values
from pyspark.sql.functions import explode, array
small_df_exploded = small_df.withColumn(
    "salted_key",
    explode(array([concat(col("join_key"), lit(f"_{i}")) for i in range(salt_buckets)]))
)
# Each row in small_df duplicated 10x with NULL_0, NULL_1, ... NULL_9

# Step C: Join on the salted key
result = large_df_salted.join(small_df_exploded, "salted_key")  # ← Now 10 tasks share the work!

# === AFTER SALTING ===
# Partition 0: [NULL_0: ~5M rows]    ← Manageable!
# Partition 1: [NULL_1: ~5M rows]    ← Manageable!
# ...
# Partition 9: [NULL_9: ~5M rows]    ← Manageable!
# Each task takes ~4 minutes instead of one task taking 40 minutes

All fix options in order of preference:

python β€” editable
# Option 1: Enable AQE skew join (EASIEST β€” try this first)
spark.conf.set("spark.sql.adaptive.enabled", "true")               # ← Enable AQE
spark.conf.set("spark.sql.adaptive.skewJoin.enabled", "true")       # ← Auto-detect and fix skew
spark.conf.set("spark.sql.adaptive.skewJoin.skewedPartitionFactor", "5")  # ← Task 5x median = skewed

# Option 2: Broadcast if other side is small (<100 MB)
result = large_df.join(broadcast(small_df), "key")  # ← Small table copied to all executors, no shuffle

# Option 3: Salt the skewed key (shown above β€” use when AQE isn't enough)

# Option 4: Isolate hot keys and handle separately
hot_keys = ["NULL", "UNKNOWN"]                                       # ← Known hot keys
hot = large_df.filter(col("key").isin(hot_keys)).join(broadcast(small_df), "key")  # ← Broadcast for hot
normal = large_df.filter(~col("key").isin(hot_keys)).join(small_df, "key")         # ← Normal join for rest
result = hot.union(normal)                                           # ← Combine results

Interview Tip: Walk through the diagnosis step by step: "First I'd check the Spark UI tasks tab to confirm skew. Then I'd identify the hot key with a groupBy count. Then I'd try AQE first, broadcast second, and salting third." Interviewers love a structured approach.

What NOT to Say: "I'd just increase the number of partitions." Repartitioning does NOT fix skew β€” if one key has 50M rows, all 50M still go to one partition regardless of partition count.

Answer First: Partitions are how Spark divides your data into chunks for parallel processing. Too few partitions means each chunk is too big (OOM risk, underutilized cores). Too many partitions means overhead from scheduling thousands of tiny tasks.

Memory Map: you determine the optimal number of partitions -> input size and target task bytes estimate partition count -> available cores set useful parallelism floor -> shuffle overhead limits excessive tasks -> duration and spill distribution refine count [05_Performance_Tuning_and_Production.md:267].

Q4: How do you determine the optimal number of partitions?

Simple Explanation: Partitions are how Spark divides your data into chunks for parallel processing. Too few partitions means each chunk is too big (OOM risk, underutilized cores). Too many partitions means overhead from scheduling thousands of tiny tasks. The sweet spot is 128-200 MB per partition.

Analogy: Imagine splitting a pizza. 2 slices for 10 people means people wait (too few partitions). 1,000 slices means more time cutting than eating (too many partitions). The right number of slices matches the number of people and their appetite.

Technical depth:

πŸ—‚οΈRule of thumb:
Each partition should be 128 MB - 200 MB # ← Spark's default file split size
Number of partitions = total_data_size / target_partition_size
Minimum: 2x total cores (keep all cores busy) # ← 100 cores β†’ at least 200 partitions
Maximum: 100K partitions (scheduler overhead) # ← Beyond this, driver struggles
Examples:
10 GB data β†’ 10 GB / 200 MB = 50 partitions
1 TB data β†’ 1 TB / 200 MB = 5,000 partitions
100 GB shuffle β†’ 100 GB / 200 MB = 500 partitions
Configuration:
spark.sql.shuffle.partitions = 200 (default, often too low for production)
With AQE: set high (e.g., 2000), let AQE coalesce # ← AQE merges small partitions automatically
For file reads: spark.sql.files.maxPartitionBytes = 128 MB

Interview Tip: Always mention AQE as the modern answer: "In Databricks, I set shuffle partitions high and let AQE coalesce small partitions at runtime. This avoids both too-many and too-few partition problems."

What NOT to Say: "I always use the default 200." The default is rarely optimal for production workloads.

Answer First: Small files are a silent performance killer. Each file requires a separate I/O request to cloud storage (S3/ADLS). If your table has 100,000 files of 1 MB each instead of 100 files of 1 GB each, every query spends most of its time on I/O overhead rather than actual data processing.

Memory Map: you handle the small file problem -> file inventory measures fragmentation -> optimized writes reduce future creation -> compaction rewrites existing fragments -> scan latency and open requests confirm improvement [05_Performance_Tuning_and_Production.md:299].

Q5: How do you handle the small file problem?

Simple Explanation: Small files are a silent performance killer. Each file requires a separate I/O request to cloud storage (S3/ADLS). If your table has 100,000 files of 1 MB each instead of 100 files of 1 GB each, every query spends most of its time on I/O overhead rather than actual data processing.

Analogy: Imagine ordering 1,000 packages of one item each vs. 10 boxes of 100 items each. The delivery truck makes 1,000 trips vs. 10 trips. Same total items, but massively different delivery time. Small files = 1,000 tiny packages.

Technical depth:

sql
-- Detect: Check file count and sizes
DESCRIBE DETAIL my_table;  -- numFiles, sizeInBytes
-- If numFiles = 50,000 and sizeInBytes = 5 GB β†’ avg file = 100 KB β†’ SMALL FILE PROBLEM!

-- Fix 1: Compact existing files (immediate relief)
OPTIMIZE my_table;                                      -- ← Rewrites small files into ~1 GB files

-- Fix 2: Prevent future small files (long-term fix)
ALTER TABLE my_table SET TBLPROPERTIES (
    'delta.autoOptimize.optimizeWrite' = 'true',        -- ← Coalesces partitions on write
    'delta.autoOptimize.autoCompact' = 'true'            -- ← Runs mini-OPTIMIZE after writes
);

-- Fix 3: Repartition before write (batch jobs)
df.repartition(10).write.format("delta") \
    .mode("append").saveAsTable("my_table")             -- ← Forces exactly 10 output files

-- Fix 4: For streaming, increase trigger interval
.trigger(processingTime="5 minutes")                    -- ← Instead of "10 seconds" = fewer, bigger files

-- Fix 5: Use Liquid Clustering for new tables (best long-term solution)
CREATE TABLE my_table (...) CLUSTER BY (col1, col2);   -- ← Auto-manages file layout

Interview Tip: Mention both reactive (OPTIMIZE) and proactive (autoOptimize, Liquid Clustering) solutions. Say: "For existing tables I run OPTIMIZE, but for new tables I enable auto-optimize or use Liquid Clustering to prevent the problem entirely."

What NOT to Say: "Just run OPTIMIZE every day." Without understanding root cause (streaming micro-batches, over-partitioned writes), you are treating symptoms not causes.

Answer First: These two configs control how Spark groups files into partitions when reading. maxPartitionBytes sets the maximum partition size. openCostInBytes adds a "penalty" for each file opened, which prevents Spark from putting too many small files into one partition.

Memory Map: the difference between spark.sql.files.maxPartitionBytes and spark.sql.files.openCostInBytes -> file scan groups small inputs for scheduling -> maximum bytes caps each combined partition -> open cost biases how many files Spark packs together -> input-task metrics verify balanced reads [05_Performance_Tuning_and_Production.md:338].

Q6: What is the difference between spark.sql.files.maxPartitionBytes and spark.sql.files.openCostInBytes?

Simple Explanation: These two configs control how Spark groups files into partitions when reading. maxPartitionBytes sets the maximum partition size. openCostInBytes adds a "penalty" for each file opened, which prevents Spark from putting too many small files into one partition.

Analogy: Think of packing a suitcase. maxPartitionBytes is the suitcase size limit (128 MB). openCostInBytes is like saying "each item takes 4 MB of space just for the wrapping." If you have 100 tiny items, the wrapping alone fills the suitcase β€” so you need more suitcases. Setting wrapping to 0 means you can cram all tiny items into fewer suitcases.

Technical depth:

ConfigDefaultPurpose
maxPartitionBytes128 MBMax size of a partition when reading files
openCostInBytes4 MBEstimated "cost" of opening a file

Key insight: openCostInBytes inflates the apparent size of small files. With 10,000 files of 1 KB each:

  • With openCostInBytes=4 MB: Each file appears as 4 MB -> 10,000 partitions (too many!)
  • With openCostInBytes=0: Files combined aggressively -> fewer partitions
python β€” editable
spark.conf.set("spark.sql.files.openCostInBytes", "0")            # ← Remove per-file overhead penalty
spark.conf.set("spark.sql.files.maxPartitionBytes", "268435456")   # ← 256 MB β€” pack more data per partition

Interview Tip: This is a deep-cut question. If you can explain openCostInBytes, it shows you have debugged real small-file problems, not just textbook knowledge.

What NOT to Say: "I don't know what openCostInBytes does." Instead say: "It controls how Spark accounts for the overhead of opening each file when deciding how to group files into partitions."

SECTION 1.5: RESOURCE TUNING & AQE

Answer First: Tuning Spark resources is about balancing CPU, memory, and parallelism. Too many cores per executor means memory contention.

Memory Map: you calculate executor memory and cores for a Spark cluster -> workload memory per task sets executor demand -> core count controls simultaneous task pressure -> overhead reserves native and Python space -> utilization and garbage collection validate sizing [05_Performance_Tuning_and_Production.md:369].

Q6B: How do you calculate executor memory and cores for a Spark cluster?

Simple Explanation: Tuning Spark resources is about balancing CPU, memory, and parallelism. Too many cores per executor means memory contention. Too few cores means underutilized nodes. The goal is to maximize throughput without OOM errors.

Analogy: Think of a restaurant kitchen. Each executor is a cooking station. Cores are the number of burners. Memory is the counter space. If you give one station 16 burners but tiny counter space, food piles up and falls off (OOM). If you give one station 1 burner and a huge counter, you waste space. The sweet spot is 5 burners with enough counter for 5 dishes at once.

Resource Tuning Calculator β€” step-by-step math:

πŸ“‹ Overview
Given: Cluster with 10 nodes x 16 cores x 64 GB RAM each
Step 1: Choose executor cores
β†’ executor.cores = 5
β†’ Why 5? Balance between I/O parallelism and memory contention
β†’ Too many (16): HDFS throughput drops, GC pressure increases
β†’ Too few (1): No multi-threading benefit, too many executors to manage
Step 2: Calculate executors per node
β†’ 16 cores / 5 cores per executor = 3 executors per node
β†’ (We leave 1 core for OS/Hadoop daemons)
Step 3: Calculate memory per executor
β†’ Total usable memory = 64 GB - 2 GB (reserved for OS) = 62 GB
β†’ 62 GB / 3 executors = ~20.6 GB per executor
β†’ But Spark needs overhead: max(384 MB, 0.10 * executor.memory)
β†’ executor.memory = 18g
→ Overhead = 18g * 0.10 = 1.8g→rounded to 2g
β†’ Total per executor = 18g + 2g = 20g βœ“ (fits in 20.6g)
Step 4: Calculate total executor instances
β†’ 10 nodes x 3 executors per node = 30 executors
β†’ Reserve 1 executor for the YARN Application Master (driver)
β†’ executor.instances = 29
Final Configuration:
spark.executor.cores = 5
spark.executor.memory = 18g
spark.executor.memoryOverhead = 2g
spark.executor.instances = 29
spark.driver.memory = 18g ← Match executor memory for driver
spark.sql.shuffle.partitions = 290 ← ~2x executors x cores = 2 x 145
python β€” editable
# Apply the configuration
spark.conf.set("spark.executor.cores", "5")
spark.conf.set("spark.executor.memory", "18g")
spark.conf.set("spark.executor.memoryOverhead", "2g")
spark.conf.set("spark.executor.instances", "29")
spark.conf.set("spark.driver.memory", "18g")
spark.conf.set("spark.sql.shuffle.partitions", "290")  # ← 2x total cores = 2 x (29 x 5)

Interview Tip: Walk through the math step by step. Interviewers care about your reasoning, not memorized numbers. Say: "I start with 5 cores per executor for I/O balance, then calculate how many executors fit per node, then divide memory accordingly."

What NOT to Say: "I just use the default settings" or "I give each executor as much memory as possible." Both show lack of tuning experience.

Answer First: AQE is Spark's ability to change its execution plan WHILE the query is running, based on real data statistics collected at runtime. Without AQE, Spark creates a plan before execution and sticks to it even if the estimates were wrong. With AQE, Spark adjusts mid-flight.

Memory Map: AQE (Adaptive Query Execution) and what does it auto-fix -> initial plan uses compile-time estimates -> runtime sizes reveal inaccurate assumptions -> engine coalesces partitions or changes join strategy -> final adaptive plan records each revision [05_Performance_Tuning_and_Production.md:428].

Q6C: What is AQE (Adaptive Query Execution) and what does it auto-fix?

Simple Explanation: AQE is Spark's ability to change its execution plan WHILE the query is running, based on real data statistics collected at runtime. Without AQE, Spark creates a plan before execution and sticks to it even if the estimates were wrong. With AQE, Spark adjusts mid-flight.

Analogy β€” GPS Recalculating Route Mid-Drive:

🧠 Memory Map
Without AQE = Printing MapQuest directions before leaving
β†’ If there's an accident on the highway, you're stuck in traffic
→ Spark estimated 100 MB but actual data is 10 GB→wrong join strategy
With AQE = Using Google Maps with live traffic
β†’ Detects accident, reroutes you automatically
β†’ Spark sees actual data size at runtime, switches from SortMerge to Broadcast

What AQE auto-fixes β€” before vs after comparison:

🧠 Memory Map
PROBLEM 1: Wrong join strategy (most common)
BEFORE AQE
→ Spark estimates table at 200 MB→chooses SortMergeJoin (shuffles both sides)
→ Actual size after filter: 5 MB→should have been BroadcastHashJoin
AFTER AQE
→ AQE sees post-filter size is 5 MB→switches to BroadcastHashJoin at runtime
→ Result: No shuffle for the small side→10x faster
PROBLEM 2: Too many shuffle partitions
BEFORE AQE
β†’ spark.sql.shuffle.partitions = 2000
β†’ After aggregation, most partitions have 1 KB of data
β†’ 2000 tiny tasks = massive scheduling overhead
AFTER AQE
β†’ AQE coalesces 2000 partitions into 50 right-sized partitions
β†’ Result: 50 tasks instead of 2000β†’less overhead, faster execution
PROBLEM 3: Data skew in joins
BEFORE AQE
β†’ One partition has 500 MB, others have 5 MB
β†’ One task runs 100x longer than the rest
AFTER AQE
β†’ AQE detects the 500 MB partition and splits it into 10 sub-partitions
→ Result: 10 tasks of 50 MB instead of 1 task of 500 MB→balanced execution

How to enable (Databricks has it ON by default since DBR 11.3+):

python β€” editable
spark.conf.set("spark.sql.adaptive.enabled", "true")                        # ← Master switch
spark.conf.set("spark.sql.adaptive.coalescePartitions.enabled", "true")      # ← Merge small partitions
spark.conf.set("spark.sql.adaptive.skewJoin.enabled", "true")                # ← Fix skewed joins
spark.conf.set("spark.sql.adaptive.localShuffleReader.enabled", "true")      # ← Avoid remote shuffle reads

# Tuning thresholds:
spark.conf.set("spark.sql.adaptive.skewJoin.skewedPartitionFactor", "5")     # ← 5x median = skewed
spark.conf.set("spark.sql.adaptive.skewJoin.skewedPartitionThresholdInBytes", "256m")  # ← Min 256 MB to trigger
spark.conf.set("spark.sql.adaptive.advisoryPartitionSizeInBytes", "128m")    # ← Target partition size

Interview Tip: Say: "AQE fixes three things at runtime: wrong join strategies, too many partitions, and data skew. In Databricks, I always set shuffle partitions high and let AQE coalesce, rather than trying to guess the right number upfront."

What NOT to Say: "AQE solves all performance problems." It does not fix bad data modeling, missing predicates, or Python UDFs.

SECTION 2: UNITY CATALOG

Answer First: Unity Catalog is Databricks' centralized governance layer. Before Unity Catalog, each workspace had its own Hive Metastore β€” so permissions, tables, and lineage were siloed. Unity Catalog creates ONE place to manage access, auditing, and discovery across ALL workspaces.

Memory Map: Unity Catalog? Explain the object hierarchy -> metastore hierarchy orders catalog schema and object -> central service applies grants across workspaces -> ownership and lineage attach to governed assets -> audit logs prove enforcement [05_Performance_Tuning_and_Production.md:494].

Q7: What is Unity Catalog? Explain the object hierarchy.

Simple Explanation: Unity Catalog is Databricks' centralized governance layer. Before Unity Catalog, each workspace had its own Hive Metastore β€” so permissions, tables, and lineage were siloed. Unity Catalog creates ONE place to manage access, auditing, and discovery across ALL workspaces.

Analogy: Think of a company with 50 offices, each with its own filing system. Nobody knows what's in other offices. Unity Catalog is like moving everything into ONE central library with a single catalog card system. Every office can access the same library with consistent permissions.

Technical depth: Unity Catalog = centralized governance for all data and AI assets in Databricks.

πŸ—‚οΈMetastore (top level β€” one per region) # ← Usually one per cloud region
Catalog (logical grouping) # ← Like a database server
Schema (like a database schema) # ← Like a database
Table (managed or external) # ← Your data
View # ← Virtual table
Function # ← UDFs
Model (ML models) # ← MLflow registered models
Volume (unstructured data β€” files, images, etc.) # ← New in Unity Catalog

Three-level namespace:

sql
SELECT * FROM catalog_name.schema_name.table_name;    -- ← Always three parts
-- Example: SELECT * FROM production.sales.orders;

Interview Tip: Emphasize the "why": "Unity Catalog solves the governance problem of having separate Hive Metastores per workspace. It provides a single pane of glass for access control, auditing, and data lineage across the entire organization."

What NOT to Say: "Unity Catalog is just a newer version of Hive Metastore." It is fundamentally different β€” it is cross-workspace, supports fine-grained access, and tracks lineage automatically.

Answer First: Row-level security means different users see different rows (e.g., US team only sees US data). Column-level security means different users see different column values (e.g., HR sees full SSN, others see masked SSN).

Memory Map: you implement row-level and column-level security in Unity Catalog -> dynamic view evaluates caller identity -> mapping table determines allowed regions -> masking function transforms protected columns -> impersonation tests validate both dimensions [05_Performance_Tuning_and_Production.md:527].

Q8: How do you implement row-level and column-level security in Unity Catalog?

Simple Explanation: Row-level security means different users see different rows (e.g., US team only sees US data). Column-level security means different users see different column values (e.g., HR sees full SSN, others see masked SSN). Unity Catalog implements both using SQL functions attached to tables.

Analogy: Think of a newspaper that publishes different editions for different regions. The US edition has US news prominently (row-level). And in the kids' edition, violent details are redacted (column masking). Same newspaper, different views based on who's reading.

Technical depth:

sql
-- COLUMN MASKING: Different users see different values
CREATE FUNCTION mask_ssn(ssn STRING)
RETURN CASE
    WHEN is_member('hr_team') THEN ssn                  -- ← HR sees full SSN: 123-45-6789
    WHEN is_member('manager') THEN 'XXX-XX-' || right(ssn, 4)  -- ← Managers see: XXX-XX-6789
    ELSE '***-**-****'                                  -- ← Everyone else sees: ***-**-****
END;

ALTER TABLE employees ALTER COLUMN ssn SET MASK mask_ssn;  -- ← Attach mask to column

-- ROW-LEVEL SECURITY: Users only see their own data
CREATE FUNCTION region_access(region STRING)
RETURN CASE
    WHEN is_member('admin') THEN true                   -- ← Admins see all regions
    WHEN is_member('us_team') THEN region = 'US'        -- ← US team sees only US rows
    WHEN is_member('eu_team') THEN region = 'EU'        -- ← EU team sees only EU rows
    ELSE false                                          -- ← Everyone else sees nothing
END;

ALTER TABLE sales SET ROW FILTER region_access ON (region);  -- ← Attach filter to table

Interview Tip: Mention that these are enforced at the query engine level β€” even Spark jobs and BI tools respect the masks and filters. Users cannot bypass them.

What NOT to Say: "We handle security in the application layer." That is error-prone. Unity Catalog enforces it at the data layer, which is more secure.

Answer First: Unity Catalog captures table- and column-level lineage from supported workloads without application-side tracking code. The resulting graph shows data origins, transformations, and downstream dependencies.

Memory Map: data lineage work in Unity Catalog -> supported workloads emit input output and expression metadata -> service builds table and column dependency edges -> explorer answers upstream and downstream impact -> audit records preserve observed lineage [05_Performance_Tuning_and_Production.md:564].

Q9: How does data lineage work in Unity Catalog?

Simple Explanation: Data lineage answers: "Where did this data come from, and where does it go?" Unity Catalog automatically tracks this at the column level, without any extra code or configuration. Every time a notebook, job, or DLT pipeline reads or writes data, Unity Catalog records the connection.

Analogy: Think of a family tree. You can trace any person back to their ancestors and forward to their descendants. Data lineage is the family tree of your data β€” you can trace any column back to its source and forward to every downstream table that uses it.

Technical depth: Unity Catalog automatically tracks lineage at the column level:

  • Which tables feed into which tables
  • Which columns are derived from which source columns
  • Lineage captured from notebooks, jobs, DLT pipelines
  • Viewable in the Catalog Explorer UI

Use cases:

  1. Impact analysis: "If I change column X, what downstream tables are affected?"
  2. Root cause analysis: "This gold table has wrong values β€” where does this column come from?"
  3. Compliance: "Show me all tables that process PII data"
  4. Data quality debugging: "Trace the source of these null values"

Interview Tip: Say: "Lineage is automatic in Unity Catalog. I use it primarily for impact analysis before making schema changes, and for root cause analysis when data quality issues arise."

What NOT to Say: "We manually document lineage in a spreadsheet." That approach does not scale and goes stale immediately.

Answer First: When Databricks needs to access data in your cloud storage (S3, ADLS, GCS), it needs permission. Storage Credentials hold the cloud credentials (IAM role, service principal).

Memory Map: cross-cloud external location -> IAM role or service principal establishes trust -> governed path binds the credential -> table access and cloud audit events verify the boundary [05_Performance_Tuning_and_Production.md:590].

Q10: What are Storage Credentials and External Locations?

Simple Explanation: When Databricks needs to access data in your cloud storage (S3, ADLS, GCS), it needs permission. Storage Credentials hold the cloud credentials (IAM role, service principal). External Locations map a storage path to a credential, telling Unity Catalog "this path is governed by me."

Analogy: A Storage Credential is like a master key card. An External Location is like assigning that key card to a specific floor of the building. "This key card (credential) opens rooms on floor 5 (s3://bucket/floor5/)."

Technical depth:

πŸ—‚οΈStorage Credential β†’ External Location β†’ External Table
Storage Credential:
Encapsulates a cloud credential (IAM role, service principal)
Grants Databricks access to cloud storage
Created by account admin # ← Only admins create these
External Location:
Maps a cloud storage PATH to a storage credential
Example: s3://my-bucket/data/ β†’ uses credential X
Enables Unity Catalog to govern access to that path
Created by metastore admin # ← Controls which paths are governed
External Table:
Table stored at an external location
Unity Catalog governs access # ← Permissions enforced even though data is external
Data survives table drop # ← Unlike managed tables
sql
-- Create storage credential (admin)
CREATE STORAGE CREDENTIAL my_s3_cred
WITH IAM_ROLE = 'arn:aws:iam::123456789:role/databricks-access';

-- Create external location
CREATE EXTERNAL LOCATION my_data_location
URL 's3://my-bucket/data/'
WITH (STORAGE CREDENTIAL my_s3_cred);

-- Create external table at that location
CREATE TABLE my_catalog.my_schema.orders
LOCATION 's3://my-bucket/data/orders/';

Interview Tip: Explain the hierarchy: "Credentials grant access to cloud storage. Locations map paths to credentials. Tables are created at locations. This separation of concerns means I can rotate credentials without touching tables."

What NOT to Say: "We just mount the S3 bucket using dbutils.fs.mount." Mounting bypasses Unity Catalog governance entirely. External Locations are the modern approach.

Answer First: This is a common real-world project. You are moving from the old per-workspace Hive Metastore to the centralized Unity Catalog. It involves migrating tables, permissions, and updating all code to use the new three-level namespace (catalog.schema.table).

Memory Map: Unity Catalog cutover -> clone or register data in the target namespace -> translate grants and update three-part references -> dual-run validation precedes workspace migration [05_Performance_Tuning_and_Production.md:639].

Q11: How do you migrate from Hive Metastore to Unity Catalog?

Simple Explanation: This is a common real-world project. You are moving from the old per-workspace Hive Metastore to the centralized Unity Catalog. It involves migrating tables, permissions, and updating all code to use the new three-level namespace (catalog.schema.table).

Analogy: It is like moving from separate department filing cabinets to a centralized digital document management system. You need to move all the files, set up new access permissions, and update everyone's bookmarks.

Technical depth:

πŸ—‚οΈMigration Steps:
1. PREPARATION
Audit existing Hive tables (managed vs external) # ← DESCRIBE EXTENDED on each table
Map Hive databases β†’ UC catalogs/schemas # ← Plan your namespace structure
Review existing permissions # ← SHOW GRANTS on tables/databases
Plan naming conventions # ← e.g., raw.sales.orders
2. INFRASTRUCTURE
Create metastore and assign to workspace # ← One per region
Create storage credentials for cloud storage # ← IAM roles / service principals
Create external locations # ← Map storage paths
Create catalogs and schemas # ← Build the namespace tree
3. TABLE MIGRATION
For managed tables: CREATE TABLE ... AS SELECT * FROM hive_metastore.db.table
For external tables: CREATE TABLE ... LOCATION 'existing_path'
Or use SYNC command (Databricks utility) # ← Copies metadata, not data
Validate row counts and checksums # ← Always verify!
4. PERMISSION MIGRATION
Map Hive privileges β†’ UC GRANT statements
Apply row/column security where needed
Test with each user group # ← Don't skip this
5. CODE UPDATES
Update all notebooks: db.table β†’ catalog.schema.table # ← The most tedious part
Update all jobs and pipelines
Update BI tool connections
6. VALIDATION & CUTOVER
Run parallel for 1-2 weeks # ← Both old and new active
Compare query results
Deprecate Hive metastore references
Tool: Databricks UCX (Unity Catalog Migration) automates much of this.

Interview Tip: Mention UCX and the parallel-run period. Say: "In practice, the hardest part is updating all downstream code references. I'd use UCX to automate the table migration and run both systems in parallel for validation before cutting over."

What NOT to Say: "Just recreate all the tables." Migration requires careful planning around permissions, lineage, and downstream dependencies.

SECTION 3: PHOTON ENGINE

Answer First: Photon is Databricks' secret weapon β€” a query engine written in C++ that replaces Spark's Java/Scala-based execution for supported operations.

Memory Map: the Photon engine? When does it help and when doesn't it -> native engine replaces supported JVM operators -> vector batches improve cache locality -> unsupported expressions fall back safely -> operator timings show coverage and speedup [05_Performance_Tuning_and_Production.md:694].

Q12: What is the Photon engine? When does it help and when doesn't it?

Simple Explanation: Photon is Databricks' secret weapon β€” a query engine written in C++ that replaces Spark's Java/Scala-based execution for supported operations. Because C++ is closer to the hardware (no JVM garbage collection, direct memory management), it runs SQL and DataFrame operations much faster.

Analogy: Spark's default engine is like reading a recipe in English and translating each step to French as you cook (JVM interpretation). Photon is like having the recipe already written in French for a French chef (native C++ execution). No translation overhead means faster cooking.

Technical depth:

When it helps (significantly):

OperationImprovement
Table scansUp to 12x faster
Aggregations3-10x faster
Joins (hash, sort-merge)3-8x faster
Filters5-10x faster
MERGE/UPDATE/DELETE3-5x faster
Delta Lake writes2-4x faster

When it does NOT help:

OperationWhy
Python UDFsUDFs run in Python, bypassing Photon entirely
RDD-based operationsPhoton only optimizes DataFrame/SQL
Very small dataPhoton overhead not worth it for < 100 MB
ML training (MLlib)Use GPU clusters instead
Unsupported operationsFalls back to Spark gracefully (no error)

How to enable:

python β€” editable
# Select Photon runtime when creating cluster (e.g., 14.3 LTS Photon)
# Or set:
spark.conf.set("spark.databricks.photon.enabled", "true")
# No code changes needed!  ← This is the key selling point

Interview Tip: Say: "Photon gives the biggest bang for zero code changes. I enable it for all SQL-heavy and ETL workloads. For Python UDF-heavy workloads, Photon won't help β€” I'd focus on replacing UDFs with built-in functions instead."

What NOT to Say: "Photon makes everything faster." It does not help Python UDFs, RDD operations, or ML training.

SECTION 4: SHUFFLE, BROADCAST & CACHING

Answer First: A shuffle happens when Spark needs to move data between executors. This happens during joins (matching keys across partitions), aggregations (groupBy), and repartitioning.

Memory Map: a shuffle and why is it the #1 performance killer -> wide dependency repartitions records by key -> map outputs write intermediate blocks -> reducers fetch data across the network -> serialization spill and transfer dominate cost [05_Performance_Tuning_and_Production.md:738].

Q12B: What is a shuffle and why is it the #1 performance killer?

Simple Explanation: A shuffle happens when Spark needs to move data between executors. This happens during joins (matching keys across partitions), aggregations (groupBy), and repartitioning. Shuffling means serializing data, writing it to disk, sending it over the network, and deserializing it on the other end. It is by far the most expensive operation in Spark.

Analogy β€” Moving Furniture Between Apartments:

πŸ—‚οΈA shuffle is like moving furniture between apartments:
Step 1: PACK (serialize) β†’ Wrap each piece of furniture in bubble wrap
Step 2: LOAD (write to disk) β†’ Load everything onto the moving truck
Step 3: DRIVE (network) β†’ Drive the truck across town
Step 4: UNLOAD (read) β†’ Carry everything into the new apartment
Step 5: UNPACK (deserialize) β†’ Unwrap everything and set it up
Now imagine doing this for EVERY join and groupBy in your pipeline.
If your query has 5 shuffles and each moves 100 GB, that's 500 GB of data
being packed, trucked, and unpacked. THAT is why shuffle is the #1 killer.
How to reduce shuffles:
Broadcast small tables (no shuffle needed β€” handouts, not moving trucks)
Co-partition tables that are frequently joined
Use AQE to auto-optimize join strategies
Pre-aggregate before joining (reduce data before shuffle)

Interview Tip: When discussing any performance issue, first check: "How much data is being shuffled?" The Spark UI Stages tab shows Shuffle Read/Write sizes. Reducing shuffle is almost always the highest-impact optimization.

What NOT to Say: "Shuffles are just Spark moving data around, it's normal." While shuffles are sometimes unavoidable, treating them as "normal" shows a lack of tuning awareness.

Answer First: A broadcast join copies the small table to every executor, so each executor can do the join locally without shuffling. A sort-merge join shuffles BOTH tables so matching keys end up on the same executor, then sorts and merges them.

Memory Map: you use broadcast joins vs sort-merge joins -> small relation must fit driver and executor memory -> broadcast avoids partitioning both inputs -> large relations use sorted exchanges -> plan statistics and skew determine the safe strategy [05_Performance_Tuning_and_Production.md:770].

Q12C: When should you use broadcast joins vs sort-merge joins?

Simple Explanation: A broadcast join copies the small table to every executor, so each executor can do the join locally without shuffling. A sort-merge join shuffles BOTH tables so matching keys end up on the same executor, then sorts and merges them. Broadcast is faster but only works when one side is small enough to fit in memory.

Analogy β€” Broadcast = Distributing Handouts:

Broadcast Join:
Teacher (small table) makes 30 copies of a handout and gives one to each student.
Each student matches their answers locally. No one needs to move.
β†’ Fast, but only works if the handout is small enough to photocopy.
Sort-Merge Join:
All students line up in alphabetical order (sort + shuffle).
Students with matching names stand next to each other (merge).
β†’ Works for any size, but everyone has to move around (expensive).
python β€” editable
# Broadcast join β€” use when one side is < 100 MB (configurable)
from pyspark.sql.functions import broadcast
result = large_df.join(broadcast(small_df), "key")  # ← Force broadcast even if Spark wouldn't auto-choose

# Auto-broadcast threshold (default: 10 MB in OSS Spark, 100 MB in Databricks)
spark.conf.set("spark.sql.autoBroadcastJoinThreshold", "100m")  # ← Tables under 100 MB auto-broadcast

Interview Tip: Say: "I default to broadcast when one side is under 100 MB. For larger tables, I rely on AQE to dynamically choose the best join strategy based on runtime statistics."

What NOT to Say: "I always use broadcast joins for better performance." Broadcasting a 10 GB table will cause OOM on every executor.

Answer First: Caching stores a DataFrame in memory so it does not need to be recomputed every time it is used. This is useful when you use the same DataFrame multiple times in a pipeline. But caching uses executor memory, which means less memory for shuffles and processing.

Memory Map: you cache data, and what are the tradeoffs -> repeated expensive lineage creates reuse value -> storage level chooses memory disk and serialization -> eviction can force recomputation -> hit rate and memory pressure justify retention [05_Performance_Tuning_and_Production.md:803].

Q12D: When should you cache data, and what are the tradeoffs?

Simple Explanation: Caching stores a DataFrame in memory so it does not need to be recomputed every time it is used. This is useful when you use the same DataFrame multiple times in a pipeline. But caching uses executor memory, which means less memory for shuffles and processing.

Analogy β€” Caching = Kitchen Counter vs. Pantry:

Without caching:
Every time you need salt, you walk to the pantry (recompute from source).
If your recipe uses salt 10 times, you make 10 trips.
With caching:
You put the salt on the counter (in memory) the first time.
The next 9 times, it's right there. No trips to the pantry.
But the counter has limited space (executor memory).
If you put too many ingredients on the counter, there's no room to chop vegetables
(shuffle/processing memory). That's the tradeoff.
python β€” editable
# Cache a frequently-used DataFrame
cleaned_df = raw_df.filter(...).select(...).cache()  # ← Marks for caching
cleaned_df.count()  # ← Triggers materialization (cache is lazy!)

# Use it multiple times without recomputation
agg1 = cleaned_df.groupBy("region").agg(sum("amount"))
agg2 = cleaned_df.groupBy("product").agg(avg("amount"))
agg3 = cleaned_df.filter(col("status") == "active").count()

# ALWAYS unpersist when done β€” free the memory!
cleaned_df.unpersist()  # ← Don't forget this!

When to cache vs when NOT to:

CacheDon't Cache
DataFrame used 3+ timesDataFrame used once
Expensive computation (joins, aggregations)Simple filter/select
Data fits in memoryData larger than available memory
Interactive explorationProduction batch job (use Delta table instead)

Interview Tip: Always mention unpersist(). Say: "I cache intermediate DataFrames that are reused multiple times, but I always unpersist when done to avoid memory pressure on downstream operations."

What NOT to Say: "I cache everything for better performance." Over-caching causes memory pressure, spills to disk, and can actually slow things down.

SECTION 5: FILE FORMAT & INDEXING

Answer First: Parquet is a columnar file format β€” it stores data by column instead of by row. This means if your query only needs 3 columns out of 100, Spark reads only those 3 columns.

Memory Map: Parquet + Snappy, and when would you choose differently -> parquet snappy and would choose differently sets the encoding and reader-efficiency trade-off -> query pattern defines projection needs -> columnar encoding and codec trade CPU for size -> reader prunes and decodes data -> bytes scanned validate the format choice [05_Performance_Tuning_and_Production.md:853].

Q12E: Why Parquet + Snappy, and when would you choose differently?

Simple Explanation: Parquet is a columnar file format β€” it stores data by column instead of by row. This means if your query only needs 3 columns out of 100, Spark reads only those 3 columns. Combined with Snappy compression (fast, moderate ratio), it is the default choice for 95% of Spark workloads.

Analogy: Row format (CSV) is like a bookshelf organized by book. To find all red books, you check every book. Column format (Parquet) is like a bookshelf organized by property: one shelf for colors, one for sizes. To find all red books, you only look at the color shelf.

πŸ—‚οΈFile Format Decision Guide:
Parquet + Snappy β†’ DEFAULT for everything (fast read, good compression)
Parquet + ZSTD β†’ When storage cost matters more than read speed (30% smaller)
Delta (Parquet) β†’ For tables that need ACID, time travel, MERGE
CSV/JSON β†’ ONLY for ingestion (landing zone), convert to Delta immediately
Avro β†’ Schema evolution in streaming (Kafka), row-based access patterns
NEVER use CSV/JSON for analytics tables:
No column pruning (must read entire row)
No predicate pushdown (must scan everything)
No compression efficiency (text-based)
No schema enforcement (string parsing errors)

Interview Tip: If asked "what file format do you use?", say: "Delta Lake, which is Parquet underneath with ACID guarantees. I use Snappy compression by default and ZSTD when I need smaller files for cold storage."

What NOT to Say: "We store data in CSV because it's simple." CSV is for human readability, not for analytics at scale.

Delta integration summary β€” Q12F: What is Z-ORDER and how does Liquid Clustering improve on it?: Databricks can schedule layout maintenance, while the Z-ORDER versus liquid-clustering mechanics and OPTIMIZE behavior are owned by the Delta performance chapter. Open the canonical Delta answer.

SECTION 6: DATABRICKS WORKFLOWS & ORCHESTRATION

Answer First: Databricks Workflows is the native orchestrator built into Databricks. Choose Airflow when you need to orchestrate across multiple platforms.

Memory Map: Databricks Workflows vs Apache Airflow. When would you use each -> databricks workflows vs apache airflow would use each contributes one dependency or data-passing rule to orchestration -> dependency graph orders tasks -> parameters and identity bind the run -> retries and timeouts handle failure -> run history and alerts prove completion [05_Performance_Tuning_and_Production.md:918].

Q13: Compare Databricks Workflows vs Apache Airflow. When would you use each?

Simple Explanation: Databricks Workflows is the native orchestrator built into Databricks. Apache Airflow is an external, general-purpose orchestrator. Choose Workflows when your pipeline lives entirely in Databricks. Choose Airflow when you need to orchestrate across multiple platforms.

Analogy: Workflows is like a building's internal intercom system β€” great for communication within the building. Airflow is like a phone system β€” works across buildings, cities, even countries. Use the intercom for internal calls, the phone for external ones.

Technical depth:

AspectDatabricks WorkflowsApache Airflow
IntegrationNative to DatabricksExternal orchestrator
SetupZero setupRequires deployment/maintenance
Task typesNotebook, Python, SQL, DLT, dbt, JARAny operator (cloud APIs, Spark, Python, etc.)
Repair/RetryBuilt-in (re-run only failed tasks)Task-level retry, no native repair
Multi-platformDatabricks onlyAny cloud/tool (heterogeneous)
CostIncluded in DatabricksSeparate infrastructure
ComplexitySimple DAGsComplex DAGs, branching, sensors
VersioningDatabricks Asset BundlesGit-synced DAGs

Use Workflows when: Pipeline is Databricks-centric Use Airflow when: Orchestrating across multiple platforms (Databricks + Snowflake + APIs + dbt)

Interview Tip: Say: "I use Workflows for Databricks-native pipelines because of zero setup and built-in repair. For cross-platform orchestration, I use Airflow with the Databricks provider to trigger Databricks jobs as tasks in a larger DAG."

What NOT to Say: "Airflow is always better because it's more flexible." If everything is in Databricks, Workflows is simpler, cheaper, and has better integration.

Answer First: A Job Cluster is created fresh for each job run and destroyed when the job finishes. An All-Purpose Cluster runs continuously and is shared among multiple users for development and exploration.

Memory Map: the difference between Job Cluster and All-Purpose Cluster -> ephemeral capacity starts per scheduled run -> interactive capacity remains available for users -> isolation and idle time drive effective cost -> run history and utilization support the choice [05_Performance_Tuning_and_Production.md:946].

Q14: What is the difference between Job Cluster and All-Purpose Cluster?

Simple Explanation: A Job Cluster is created fresh for each job run and destroyed when the job finishes. An All-Purpose Cluster runs continuously and is shared among multiple users for development and exploration. Job Clusters are cheaper and isolated. All-Purpose Clusters are convenient but expensive.

Analogy: A Job Cluster is like renting a car for a road trip β€” you only pay for the trip duration, then return it. An All-Purpose Cluster is like owning a car β€” it sits in your garage (costing money) even when you're not driving.

Technical depth:

AspectJob ClusterAll-Purpose Cluster
LifecycleCreated per job run, destroyed afterLong-running, manually managed
CostCheaper (job compute pricing)More expensive (all-purpose pricing)
SharingSingle jobMultiple users/notebooks
StartupSlower (cluster creation)Instant (already running)
Use caseProduction jobsDevelopment, ad-hoc queries
Auto-terminationAfter job completesConfigurable timeout

Best practice: Always use Job Clusters for production. Use All-Purpose only for development.

Interview Tip: Say: "In production, we exclusively use Job Clusters β€” they're cheaper and provide isolation between jobs. We enforce this through cluster policies that prevent production jobs from using All-Purpose compute."

What NOT to Say: "We use All-Purpose Clusters for everything because they're already running." That is one of the most common cost mistakes in Databricks.

Answer First: Databricks Workflows lets tasks communicate using task values β€” simple key-value pairs that one task sets and another task reads. This is how you pass metadata like row counts, dates, or status flags between steps in a pipeline.

Memory Map: you pass parameters between tasks in a Workflow -> producer writes a named task value -> workflow context stores the small payload -> downstream task references the upstream key -> missing-value handling protects retries [05_Performance_Tuning_and_Production.md:971].

Q15: How do you pass parameters between tasks in a Workflow?

Simple Explanation: Databricks Workflows lets tasks communicate using task values β€” simple key-value pairs that one task sets and another task reads. This is how you pass metadata like row counts, dates, or status flags between steps in a pipeline.

Analogy: Think of a relay race. Runner 1 passes a baton (task value) to Runner 2. The baton carries information about what happened in the previous leg. Without the baton, Runner 2 doesn't know the race state.

Technical depth:

python β€” editable
# Task 1: Set task values (the "sender")
dbutils.jobs.taskValues.set(key="record_count", value=15000)      # ← Pass row count downstream
dbutils.jobs.taskValues.set(key="max_date", value="2025-06-15")   # ← Pass watermark date
dbutils.jobs.taskValues.set(key="status", value="success")        # ← Pass status flag

# Task 2: Get values from Task 1 (the "receiver")
count = dbutils.jobs.taskValues.get(taskKey="task_1", key="record_count")   # ← Read from specific task
max_date = dbutils.jobs.taskValues.get(taskKey="task_1", key="max_date")
status = dbutils.jobs.taskValues.get(taskKey="task_1", key="status")

# Conditional logic based on upstream task
if count > 10000:
    run_large_batch_processing()    # ← Different path for large batches

Interview Tip: Mention that task values are lightweight (string/int/float only, not DataFrames). For passing large data between tasks, write to a Delta table and pass the table path or version as a task value.

What NOT to Say: "We write results to a shared file and read it in the next task." Task values are the built-in mechanism β€” using shared files is fragile and error-prone.

Answer First: DABs define your Databricks resources (jobs, clusters, pipelines) as YAML configuration files that live in Git. This means you can version-control your infrastructure, review changes in PRs, and deploy consistently across dev/staging/prod environments.

Memory Map: Databricks Asset Bundles (DABs)? How do they enable CI/CD -> databricks asset bundles dabs they enable ci cd defines one artifact validation and promotion checkpoint -> versioned artifact enters validation -> tests and policy checks gate promotion -> same artifact reaches the next environment -> deployment record and rollback prove reproducibility [05_Performance_Tuning_and_Production.md:1001].

Q16: What are Databricks Asset Bundles (DABs)? How do they enable CI/CD?

Simple Explanation: DABs define your Databricks resources (jobs, clusters, pipelines) as YAML configuration files that live in Git. This means you can version-control your infrastructure, review changes in PRs, and deploy consistently across dev/staging/prod environments. It is Infrastructure-as-Code for Databricks.

Analogy: Without DABs, deploying to Databricks is like manually configuring each server by clicking through a UI. With DABs, it is like using Terraform β€” you declare what you want in a config file, and the tool makes it happen consistently every time.

Technical depth:

yaml
# databricks.yml
bundle:
  name: etl_pipeline

workspace:
  host: https://my-workspace.databricks.com

resources:
  jobs:
    daily_etl:
      name: "Daily ETL Pipeline"
      schedule:
        quartz_cron_expression: "0 0 6 * * ?"      # ← Run at 6 AM UTC daily
        timezone_id: "UTC"
      tasks:
        - task_key: bronze_ingestion
          notebook_task:
            notebook_path: ./notebooks/bronze_ingestion.py
          new_cluster:                               # ← Job cluster (created per run)
            spark_version: "14.3.x-scala2.12"
            node_type_id: "i3.xlarge"
            num_workers: 4
        - task_key: silver_transformation
          depends_on:
            - task_key: bronze_ingestion             # ← Runs after bronze completes
          notebook_task:
            notebook_path: ./notebooks/silver_transformation.py

targets:                                             # ← Deploy to different environments
  dev:
    workspace:
      host: https://dev-workspace.databricks.com
  staging:
    workspace:
      host: https://staging-workspace.databricks.com
  prod:
    workspace:
      host: https://prod-workspace.databricks.com
bash
# Deploy to different environments
databricks bundle deploy --target dev       # ← Deploy to dev workspace
databricks bundle deploy --target staging   # ← Deploy to staging workspace
databricks bundle deploy --target prod      # ← Deploy to production workspace

Interview Tip: Say: "DABs give us Git-based version control for our Databricks infrastructure. We review job configuration changes in PRs just like code changes, and deploy consistently using CI/CD pipelines."

What NOT to Say: "We configure jobs manually in the Databricks UI." Manual configuration means no version history, no code review, and no consistent deployments.

SECTION 7: COST MANAGEMENT

Answer First: Databricks costs come from two sources: compute (clusters running) and storage (data stored). The biggest waste comes from idle clusters, oversized instances, and inefficient queries.

Memory Map: you manage costs in Databricks? What strategies have you used -> manage costs in databricks strategies have used isolates the workload-specific cost driver -> tagged usage establishes spend -> idle or inefficient work becomes visible -> policy or sizing removes waste -> billing trend confirms savings without SLA loss [05_Performance_Tuning_and_Production.md:1065].

Q17: How do you manage costs in Databricks? What strategies have you used?

Simple Explanation: Databricks costs come from two sources: compute (clusters running) and storage (data stored). The biggest waste comes from idle clusters, oversized instances, and inefficient queries. Cost management is about rightsizing compute, optimizing storage, and creating visibility into who spends what.

Analogy: Managing Databricks costs is like managing an electricity bill. You turn off lights (terminate idle clusters), use energy-efficient bulbs (Photon, Spot instances), and check the meter regularly (cost dashboards). You also assign meters to each apartment (cost tagging per team).

Technical depth:

πŸ—‚οΈCost Management Strategies:
1. COMPUTE OPTIMIZATION (biggest impact β€” 60-70% of costs)
Use Job Clusters (not All-Purpose) for production # ← 30-50% savings instantly
Enable auto-termination (default: 120 min idle) # ← Stop paying for idle clusters
Use Spot/Preemptible instances (up to 90% savings) # ← Accept interruption risk for savings
Right-size clusters (analyze utilization metrics) # ← Don't use 20 nodes if 5 will do
Use Serverless for bursty workloads (no idle cost) # ← Pay only when queries run
Instance pools for faster startup (shared warm instances)
2. CLUSTER POLICIES (enforce governance)
Max nodes per cluster # ← Prevent runaway autoscaling
Allowed instance types # ← No i3.16xlarge for dev work
Auto-termination minimum # ← Force 30-min timeout
Required tags (cost allocation) # ← team=data-eng, project=etl
Restrict to Photon runtimes (more efficient)
3. STORAGE OPTIMIZATION
VACUUM unused files (free storage) # ← Old Parquet files pile up
OPTIMIZE (compact files, reduce I/O) # ← Fewer files = faster reads
Use appropriate compression (ZSTD for Delta) # ← 30% smaller than Snappy
Lifecycle policies on raw data # ← Delete 90-day-old bronze data
Delete unnecessary table versions # ← history.deletedFileRetentionDuration
4. QUERY OPTIMIZATION
Enable Photon (more queries per DBU) # ← Same work, fewer DBUs
Use SQL Warehouse for BI (auto-scale, auto-suspend) # ← Much cheaper than clusters for BI
Partition pruning + Z-ORDER (reduce scanned data) # ← Scan 1% instead of 100%
Cache frequently accessed Gold tables
5. MONITORING & CHARGEBACK
Tag clusters and jobs by team/project # ← Attribution
Use Databricks Account Console for cost analysis # ← See spend by workspace/cluster
Set up budget alerts # ← Notify when team exceeds budget
Implement showback/chargeback per team # ← Teams see their own costs
Review weekly: unused clusters, oversized instances # ← Continuous improvement

Interview Tip: Lead with the highest-impact item: "The single biggest cost savings I've driven was switching production from All-Purpose to Job Clusters β€” 40% cost reduction with zero code changes. Then I added Spot instances for another 30% savings."

What NOT to Say: "Costs are managed by the cloud team, not the data team." Data engineers should own the cost efficiency of their pipelines.

Answer First: CI/CD for Databricks means automating the testing and deployment of notebooks, jobs, and infrastructure configurations.

Memory Map: you implement CI/CD for Databricks pipelines -> Git change triggers lint and unit tests -> integration environment validates jobs and data contracts -> bundle promotes the same artifact -> deployment record enables rollback [05_Performance_Tuning_and_Production.md:1118].

Q18: How do you implement CI/CD for Databricks pipelines?

Simple Explanation: CI/CD for Databricks means automating the testing and deployment of notebooks, jobs, and infrastructure configurations. On every pull request, automated tests run. On merge to main, the pipeline deploys to staging. After approval, it deploys to production.

Analogy: Think of a car assembly line. CI is the quality inspection at each station (tests on every PR). CD is the automated conveyor belt that moves the car from assembly (dev) to paint shop (staging) to showroom (prod). No manual carrying of parts between stations.

Technical depth:

πŸ—‚οΈCI/CD Pipeline:
1. SOURCE CONTROL
Databricks Repos (Git integration) # ← Notebooks sync with Git
Notebooks, Python modules, SQL scripts in Git # ← Everything versioned
Databricks Asset Bundles (YAML configs) in Git # ← Infrastructure versioned too
2. CONTINUOUS INTEGRATION (on PR)
Run unit tests (pytest on local Spark or connect-mode)
Run linting (ruff, black, mypy) # ← Code quality
Validate DAB configs: databricks bundle validate # ← Catch config errors early
Run integration tests on dev workspace # ← Test with real Spark
Code review # ← Human review
3. CONTINUOUS DEPLOYMENT
Dev: Auto-deploy on merge to develop branch # ← Fast feedback
Staging: Deploy on merge to main, run integration tests
Prod: Manual approval gate, then deploy # ← Human gate for safety
Rollback: Redeploy previous DAB version # ← Git revert + redeploy
4. TESTING STRATEGIES
Unit tests: Mock Spark, test transformations # ← Fast, run locally
Integration tests: Nutter framework or pytest with real Spark
Data quality tests: Compare output vs expected # ← Row counts, checksums
Performance tests: Benchmark on staging data # ← Catch regressions

Example GitHub Actions:

yaml
name: Deploy Databricks Pipeline
on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: databricks/setup-cli@main
      - run: databricks bundle validate --target staging     # ← Validate config
      - run: databricks bundle deploy --target staging       # ← Deploy to staging
      - run: databricks bundle run daily_etl --target staging  # ← Integration test
      - run: databricks bundle deploy --target prod          # ← Deploy to production
        if: github.ref == 'refs/heads/main'

Interview Tip: Say: "Our CI pipeline validates DAB configs and runs unit tests on every PR. On merge, it deploys to staging and runs integration tests. Production deployment requires manual approval."

What NOT to Say: "We deploy by copying notebooks manually between workspaces." That is the opposite of CI/CD.

SECTION 8: DATA GOVERNANCE AT SCALE

Answer First: Data governance at scale means ensuring the right people can access the right data, with audit trails, quality checks, and discoverability β€” across hundreds of tables and thousands of users.

Memory Map: you handle data governance for a large organization -> domain ownership assigns accountable stewards -> centralized catalog standardizes classifications and grants -> automation applies baseline controls -> access reviews and logs prove compliance [05_Performance_Tuning_and_Production.md:1182].

Q19: How do you handle data governance for a large organization?

Simple Explanation: Data governance at scale means ensuring the right people can access the right data, with audit trails, quality checks, and discoverability β€” across hundreds of tables and thousands of users. Unity Catalog is the foundation, but governance also requires processes, policies, and tooling.

Analogy: Governing data for a large org is like running a public library system across a city. You need a card catalog (discovery), library cards with different access levels (permissions), checkout records (audit logs), book condition checks (data quality), and rules about which branches carry which books (data classification).

Technical depth:

πŸ—‚οΈGovernance Framework:
1. ACCESS CONTROL (Unity Catalog)
Principle of least privilege # ← Start with no access, grant as needed
Three-level namespace for organization # ← catalog.schema.table
Row-level and column-level security # ← Fine-grained access
Dynamic data masking for PII # ← Different users see different values
Grant/Revoke at table, schema, catalog levels # ← Hierarchical permissions
2. DATA CLASSIFICATION
Tag tables and columns with sensitivity levels # ← PUBLIC, INTERNAL, CONFIDENTIAL, RESTRICTED
PII, PHI, financial, public # ← Regulatory categories
Use tags to drive access policies # ← RESTRICTED tag β†’ only approved groups
Auto-classification tools (e.g., Privacera) # ← Scan columns for SSN, email patterns
3. AUDIT & COMPLIANCE
Unity Catalog audit logs (who accessed what, when)
Delta Lake history (DESCRIBE HISTORY for data changes)
Regulatory compliance (GDPR right to deletion, CCPA)
Retention policies (auto-delete after N years)
4. DATA QUALITY
DLT expectations at every layer # ← Quality gates
Data contracts between teams # ← Formal schema agreements
Quality dashboards and SLA monitoring # ← Freshness, completeness, accuracy
Quarantine + alerting for bad data # ← Bad records don't pollute downstream
5. DATA DISCOVERY
Unity Catalog search and browse # ← Find tables by name or description
Column descriptions and table comments # ← Self-documenting metadata
Lineage visualization # ← Trace data flow visually
Data dictionary / business glossary # ← Business terms mapped to technical tables

Interview Tip: Say: "I build governance in layers: access control with Unity Catalog, data quality with DLT expectations, and discoverability with rich metadata. The key is making governance easy to follow β€” if it's too burdensome, teams will bypass it."

What NOT to Say: "We trust our engineers to manage their own access." Trust-based governance does not scale and does not meet compliance requirements.

Delta integration summary β€” Q20: How do you handle GDPR "right to be forgotten" in Delta Lake?: Databricks governance orchestrates the subject-request workflow; Delta history, DELETE, VACUUM retention checks, and physical-erasure verification remain in the canonical Delta recovery answer. Open the canonical Delta answer.

SECTION 9: SYSTEM DESIGN (LEADERSHIP-LEVEL)

Answer First: At this scale, the platform must be self-service (engineers don't wait for a platform team), governed (no data Wild West), cost-efficient (200 engineers can burn money fast), and standardized (consistent practices across teams).

Memory Map: you design a data platform on Databricks for a company with 200+ data engineers -> workspace topology separates teams and environments -> Unity Catalog centralizes data governance -> shared deployment standards scale pipelines -> cost and audit telemetry prove platform control [05_Performance_Tuning_and_Production.md:1277].

Q21: How would you design a data platform on Databricks for a company with 200+ data engineers?

Simple Explanation: At this scale, the platform must be self-service (engineers don't wait for a platform team), governed (no data Wild West), cost-efficient (200 engineers can burn money fast), and standardized (consistent practices across teams). The architecture spans workspaces, compute, governance, CI/CD, and operational processes.

Technical depth:

πŸ—‚οΈPlatform Architecture:
1. WORKSPACE STRATEGY
Separate workspaces: Dev, Staging, Prod # ← Environment isolation
Shared workspace for ad-hoc analytics # ← Data scientists, analysts
Each workspace connected to same Unity Catalog metastore # ← Unified governance
Network isolation between environments # ← VPC peering, private endpoints
2. CATALOG STRUCTURE (Unity Catalog)
catalog: raw (bronze layer, all domains) # ← Landing zone for all sources
catalog: curated (silver layer, per domain) # ← Domain teams own their data
catalog: analytics (gold layer, cross-domain) # ← Shared analytical tables
catalog: ml (feature store, models) # ← ML team assets
catalog: sandbox (dev experimentation) # ← No governance needed here
3. COMPUTE GOVERNANCE
Cluster policies per team (max size, instance types)
Job clusters for production (enforced) # ← Policy blocks All-Purpose for prod
Serverless SQL Warehouses for BI # ← Auto-scale, auto-suspend
Instance pools for faster startup # ← Shared warm instances
Cost tags for chargeback # ← Every cluster tagged with team
4. CI/CD
Git integration (Databricks Repos)
Databricks Asset Bundles for IaC # ← All infra in Git
GitHub Actions / Azure DevOps pipelines
Automated testing framework (pytest + nutter)
Environment promotion: dev β†’ staging β†’ prod
5. DATA GOVERNANCE
Unity Catalog for all access control
Data classification and tagging
Automated PII detection
Audit logging and compliance reporting
Data quality framework (DLT expectations)
6. STANDARDS & BEST PRACTICES
Medallion architecture (enforced naming convention)
Shared Python libraries (company data framework) # ← Reusable transforms
Coding standards (linting, testing requirements)
Documentation templates
On-call rotation for production pipelines
7. COST MANAGEMENT
Budget alerts per team
Monthly cost reviews
Auto-termination policies
Spot instance usage targets (>70%)
Quarterly optimization sprints # ← Dedicated time to tune

Interview Tip: When answering system design questions, walk through the architecture methodically β€” workspace strategy, compute governance, data governance, CI/CD, cost management. Show breadth of thinking, then dive deep on whatever the interviewer asks about.

What NOT to Say: "Give everyone a shared workspace and let them figure it out." That leads to the exact chaos described in Q22.

Answer First: Instead: assess quickly, fix the highest-impact problems first (quick wins), then make structural improvements. The 80/20 rule applies β€” 20% of the changes will fix 80% of the problems.

Memory Map: Scenario β€” You're brought in to rescue a failing Databricks migration. The team has been working for 6 months but pipelines are slow, data quality is poor, and costs are 3x budget. What do you do -> baseline quantifies reliability quality and budget gaps -> evidence ranks the highest-impact causes -> quick fixes restore service while platform work removes recurrence -> SLA and spend trends measure recovery [05_Performance_Tuning_and_Production.md:1341].

Q22: Scenario β€” You're brought in to rescue a failing Databricks migration. The team has been working for 6 months but pipelines are slow, data quality is poor, and costs are 3x budget. What do you do?

Simple Explanation: This is a turnaround scenario. The key is to resist the urge to rewrite everything. Instead: assess quickly, fix the highest-impact problems first (quick wins), then make structural improvements. The 80/20 rule applies β€” 20% of the changes will fix 80% of the problems.

Analogy: You are a doctor in an emergency room. The patient (migration) has multiple issues. You don't schedule elective surgery (full rewrite). You stabilize first (quick wins), then diagnose root causes (structural fixes), then plan long-term health (optimization).

Technical depth:

πŸ—‚οΈWeek 1: ASSESS
Inventory all pipelines (notebook? workflow? DLT?)
Map data flows: source β†’ bronze β†’ silver β†’ gold
Measure: pipeline durations, failure rates, data quality scores
Analyze costs: which clusters, which jobs, which teams
Interview team: what are the blockers?
Common findings:
No medallion architecture (random file locations) # ← Data spaghetti
All-purpose clusters for everything (3x cost) # ← Most common cost issue
No OPTIMIZE/VACUUM (small file explosion) # ← Performance killer
Python UDFs everywhere (10x slower than built-in) # ← Defeats Photon
No monitoring (failures go unnoticed) # ← Silent data loss
Week 2-3: QUICK WINS (80/20 rule)
Switch production to Job Clusters (immediate cost savings)
Run OPTIMIZE + VACUUM on all tables # ← Instant perf improvement
Enable auto-compaction and optimized writes
Replace top 10 slowest Python UDFs with built-in functions
Enable AQE cluster-wide
Set up Photon for heavy workloads
Set up basic monitoring (row counts, freshness)
Week 4-8: STRUCTURAL FIXES
Implement medallion architecture (standardize layers)
Move to Unity Catalog (centralize governance)
Implement DLT for critical pipelines (quality + lineage)
Set up CI/CD with Asset Bundles
Implement data quality framework
Create cluster policies (cost control)
Week 8-12: OPTIMIZATION
Performance tune top 20 pipelines # ← Focus on the biggest jobs
Implement incremental processing (replace full refreshes)
Implement SCD Type 2 where needed
Set up cost dashboards and chargeback
Document standards and train the team
Metrics to track:
Pipeline duration (target: <1 hour for daily pipelines)
Failure rate (target: <5%)
Data freshness SLA (target: 99.5%)
Monthly cost (target: back to budget)
Data quality score (target: >99% passing expectations)

Interview Tip: Structure your answer as phases with timelines. Interviewers want to see prioritization skills. Say: "I'd spend Week 1 assessing, Weeks 2-3 on quick wins that don't require code changes, Weeks 4-8 on structural fixes, and Weeks 8-12 on optimization."

What NOT to Say: "I'd rewrite everything from scratch." That wastes 6 months of existing work and takes another 6 months.

Answer First: Lakehouse data modeling follows the medallion pattern: raw data lands in bronze (no modeling), gets cleaned in silver (light modeling), and gets optimized for consumption in gold (heavy modeling). Gold is where you build star schemas for BI or wide tables for ML.

Memory Map: you approach data modeling in a lakehouse -> approach data modeling in lakehouse sets the specific transaction and interoperability constraint -> engine ecosystem defines interoperability needs -> mutation model selects transaction semantics -> maintenance ownership sets operating cost -> cross-engine test determines fit [05_Performance_Tuning_and_Production.md:1401].

Q23: How do you approach data modeling in a lakehouse?

Simple Explanation: Lakehouse data modeling follows the medallion pattern: raw data lands in bronze (no modeling), gets cleaned in silver (light modeling), and gets optimized for consumption in gold (heavy modeling). Gold is where you build star schemas for BI or wide tables for ML.

Analogy: Think of cooking. Bronze is raw ingredients straight from the farm (unwashed, uncut). Silver is prepped ingredients (washed, chopped, organized). Gold is the finished dish (plated, garnished, ready to serve). You model for the consumer, not the source.

Technical depth:

πŸ—‚οΈLakehouse Data Modeling Strategy:
BRONZE: No modeling β€” raw as-is
Preserve source schema # ← Don't transform, just land
Add metadata columns # ← _ingested_at, _source_file, _batch_id
Append-only # ← Never update bronze
SILVER: Light modeling β€” conformed
Apply business key relationships # ← customer_id standardized across sources
Standardize naming conventions # ← snake_case, consistent types
Deduplicate and validate # ← Remove duplicates, enforce constraints
SCD Type 2 for dimensions # ← Track history of changes
Keep relatively normalized # ← Don't over-denormalize yet
GOLD: Heavy modeling β€” optimized for use case
Star/snowflake schema for BI # ← Analysts expect this
Fact tables (measures, metrics)
Dimension tables (descriptive)
Bridge tables (many-to-many)
Wide/denormalized tables for ML # ← Data scientists expect this
Feature tables (all attributes pre-joined)
Aggregation tables for dashboards # ← Pre-computed for speed
Pre-computed daily/hourly metrics
CLUSTER BY (most queried columns) # ← Optimize for access patterns
Key Principles:
1. Model for the consumer, not the source
2. Gold tables should be self-service (no complex joins)
3. Use naming conventions: fact_, dim_, agg_, feature_
4. Document business logic in table comments
5. Maintain backward compatibility (add columns, don't remove)

Interview Tip: Say: "I model differently at each layer. Bronze is schema-on-read. Silver is lightly normalized with business keys. Gold is heavily modeled for specific use cases β€” star schema for BI, wide tables for ML."

What NOT to Say: "We use one big denormalized table for everything." That works for small datasets but fails at scale due to update complexity and storage waste.

Answer First: "Nothing changed in the code" is a red herring. Many things change besides code: data volume, data distribution, table health, cluster configuration, cloud infrastructure. This is a diagnostic exercise β€” you systematically check each potential cause.

Memory Map: Scenario β€” A production pipeline that was running fine for 6 months suddenly takes 5x longer. Nothing changed in the code. What happened -> historical baseline confirms a runtime regression -> data growth, skew, or service changes explain new work -> smallest targeted adjustment restores capacity -> comparable rerun verifies the cause [05_Performance_Tuning_and_Production.md:1449].

Q24: Scenario β€” A production pipeline that was running fine for 6 months suddenly takes 5x longer. Nothing changed in the code. What happened?

Simple Explanation: "Nothing changed in the code" is a red herring. Many things change besides code: data volume, data distribution, table health, cluster configuration, cloud infrastructure. This is a diagnostic exercise β€” you systematically check each potential cause.

Analogy: Your car suddenly gets terrible gas mileage. You haven't changed how you drive. But maybe: the tires lost pressure (small files), the road got hillier (data volume increased), one cylinder is misfiring (data skew), or the gas station changed fuel quality (infrastructure change).

Technical depth:

πŸ—‚οΈInvestigation Checklist (in order of likelihood):
1. DATA VOLUME # ← Most common cause
Did the source data size increase dramatically?
(Seasonal spike, backfill, duplicate events)
β†’ Check: DESCRIBE DETAIL β€” compare sizeInBytes to last month
2. DATA SKEW # ← Second most common
Did a new customer/product become very popular?
(One partition now 100x larger than others)
β†’ Check: Spark UI tasks tab β€” one task much slower than others?
3. SMALL FILES ACCUMULATION # ← Silent killer
6 months of streaming writes without OPTIMIZE?
(10,000 small files β†’ slow reads)
β†’ Check: DESCRIBE DETAIL β€” numFiles much higher than expected?
4. STATISTICS STALE # ← Subtle but impactful
Spark using wrong join strategy (SortMerge instead of Broadcast)
because table grew past the broadcast threshold
β†’ Fix: ANALYZE TABLE ... COMPUTE STATISTICS
5. CLUSTER CHANGES # ← Check infra
Cloud provider changed instance types?
Cluster policy updated? Fewer nodes?
6. INFRASTRUCTURE # ← External factors
S3/ADLS throttling? Network issues?
Noisy neighbor on shared infrastructure?
7. UPSTREAM CHANGES # ← Source system issues
Source system changed data format?
New columns causing schema evolution?
Change in data distribution pattern?
8. DELTA TABLE HEALTH # ← Always check this
Run: DESCRIBE DETAIL my_table
Check: numFiles (too many?), sizeInBytes (unexpected growth?)
Quick Fixes:
OPTIMIZE all affected tables # ← Fix small files
ANALYZE TABLE ... COMPUTE STATISTICS # ← Fix stale stats
Check and increase spark.sql.shuffle.partitions # ← Fix shuffle bottleneck
Enable AQE if not already # ← Auto-fix several issues
Consider Liquid Clustering for affected tables # ← Long-term fix

Interview Tip: Walk through the checklist methodically. Say: "I'd start with the most common causes: data volume growth, then data skew, then small files. I'd check DESCRIBE DETAIL first for table health, then Spark UI for execution-level issues."

What NOT to Say: "If nothing changed in the code, it must be an infrastructure issue." Always check data-level issues first β€” they are the most common cause.

Answer First: Serverless means Databricks manages the compute infrastructure entirely. You don't choose instance types, configure clusters, or wait for them to start. You just run your query or job, and Databricks handles the rest.

Memory Map: Serverless in Databricks? How does it differ from standard compute -> platform provisions and scales infrastructure automatically -> workload submits without instance selection -> managed startup trades control for simplicity -> latency compatibility and billing determine suitability [05_Performance_Tuning_and_Production.md:1511].

Q25: What is Serverless in Databricks? How does it differ from standard compute?

Simple Explanation: Serverless means Databricks manages the compute infrastructure entirely. You don't choose instance types, configure clusters, or wait for them to start. You just run your query or job, and Databricks handles the rest. You pay only for the compute time your query actually uses, not for idle clusters.

Analogy: Standard clusters are like owning a car β€” you pay for insurance, parking, and gas even when it sits in the garage. Serverless is like using Uber β€” you pay only when you ride. For daily commutes (24/7 streaming), owning is cheaper. For occasional trips (bursty analytics), Uber is cheaper.

Technical depth:

AspectStandard ClustersServerless
ManagementYou manage cluster configDatabricks manages everything
Startup time3-10 minutes<10 seconds
ScalingManual or autoscale configAutomatic
BillingRunning time (even idle)Query/task execution time only
Cost controlCluster policiesAutomatic
Available forNotebooks, jobsSQL Warehouses, notebooks, jobs

When to use Serverless:

  • SQL analytics (SQL Warehouses) β€” fast startup, auto-suspend
  • Bursty workloads (intermittent jobs) β€” no idle cost
  • Development (fast startup, no idle costs) β€” try ideas quickly
  • Teams without Spark expertise (no tuning needed) β€” platform handles it

When NOT to use:

  • Heavy streaming (24/7 processing β€” predictable load is cheaper on standard clusters)
  • GPU workloads (ML training β€” Serverless doesn't support GPUs)
  • Very large clusters with specific hardware requirements

Interview Tip: Say: "I use Serverless SQL Warehouses for all BI workloads β€” 10-second startup and zero idle cost. For batch ETL, I use Job Clusters with Spot instances. For streaming, I use standard clusters since the workload is continuous."

What NOT to Say: "Serverless is always better because you don't manage anything." For predictable, continuous workloads, standard clusters with reserved instances are significantly cheaper.

Answer First: Production debugging begins with the failed task and first causal error, then correlates Spark UI, logs, data volume, infrastructure events, and recent changes. Apply one safe change and verify recovery with the same evidence.

Memory Map: Quick-fire: Common production issues and their solutions -> first causal error anchors investigation -> plan logs and data metrics isolate the failure class -> smallest safe correction restores the run -> same evidence verifies recovery [05_Performance_Tuning_and_Production.md:1545].

Q26: Quick-fire: Common production issues and their solutions.

IssueCauseSolution
ConcurrentModificationExceptionConcurrent writes to same tablePartition writes by time, use WAP pattern
VACUUM retention too shortConcurrent readers failKeep default 7-day retention
Pipeline silently produces wrong dataNo data quality checksAdd DLT expectations, row count validation
Costs spike on weekendsDevelopers leave clusters runningEnforce auto-termination in cluster policies
Query suddenly slowStatistics outdatedRun ANALYZE TABLE COMPUTE STATISTICS
Streaming job keeps restartingState store corruptionDelete checkpoint, reprocess from source
SchemaEvolutionExceptionSource schema changedUse Auto Loader rescue mode
FileNotFoundException during queryVACUUM ran while query executingIncrease VACUUM retention
Cluster OOM on startupToo many librariesUse init scripts, minimize dependencies
Job works in dev, fails in prodDifferent cluster configUse Databricks Asset Bundles for consistency

Interview Tip: When asked about production issues, always give the triad: symptom, root cause, and fix. This shows you have actually debugged these problems, not just read about them.

What NOT to Say: "We haven't had production issues." Every data platform has issues. The question is whether you detect them quickly and fix them systematically.

Production, CI/CD, Cost Management & Mock Interview

πŸ’‘ Interview Tip
Time: 5-6 hours | Priority: HIGH β€” Production experience separates senior from mid-level Context: Azure DevOps, cost governance, 200+ engineers, regulatory compliance Approach: Every topic starts with simple explanation β†’ then interview-level depth

MEMORY MAP: PRODUCTION & CI/CD β†’ DWCCM

🧠 D β†’ Deployment (DABs, CI/CD pipeline, Git integration)
DDeployment (DABs, CI/CD pipeline, Git integration)
WWorkflows (Jobs, Tasks, dependencies, retries)
CCost Management (spot instances, autoscaling, cluster policies)
CCluster Types (all-purpose vs job vs SQL warehouse)
MMonitoring (alerts, Ganglia, log4j, custom metrics)

How to use: Before answering ANY production/CI/CD question, mentally walk through DWCCM. Most questions touch 2-3 of these areas. Mentioning all five shows breadth.

SECTION 1: DATABRICKS WORKFLOWS & ORCHESTRATION (1 hour)

Q1: What is Databricks Workflows? How does it compare to Apache Airflow?

Simple Explanation: A workflow is a scheduled pipeline β€” a series of tasks that run in order. For example: "Every day at 6am, run bronze ingestion β†’ then silver transformation β†’ then gold aggregation."

Databricks Workflows is Databricks' built-in job scheduler. You define tasks (notebooks, SQL, Python scripts), set dependencies (task B runs after task A), and schedule them.

Apache Airflow is a separate open-source tool that does the same thing but works across ANY platform (not just Databricks). It's more flexible but requires more setup and maintenance.

Real-world analogy:

  • Databricks Workflows = Your company's internal task management tool (simple, built-in, works only within your company)
  • Apache Airflow = A universal project management tool (powerful, works everywhere, but you need to install and maintain it yourself)
AspectDatabricks WorkflowsApache Airflow
SetupZero β€” already built into DatabricksRequires deployment, servers, maintenance
Task typesNotebook, Python, SQL, Lakeflow, dbt, JARAny cloud/tool (Databricks, Snowflake, APIs, anything)
Repair/RetryBuilt-in β€” re-run ONLY failed tasks (smart!)Task retry, but no native "repair" feature
Multi-platformDatabricks only β€” can't orchestrate Snowflake or external APIsWorks across ANY platform (biggest advantage)
CostFree (included in Databricks)Separate infrastructure cost
VersioningDeclarative Automation Bundles (YAML in Git)Git-synced Python DAGs
Job backfillsBuilt-in GA (2025) β€” reprocess historical dataBuilt-in backfill support

When to use which:

  • Workflows: When your pipeline is 100% inside Databricks
  • Airflow: When you orchestrate across multiple platforms (Databricks + Oracle + external APIs)
  • Azure Data Factory: Alternative to Airflow for Azure-centric orchestration

Example answer: "We use Databricks Workflows for our Databricks-native ETL pipelines. For cross-platform orchestration (Oracle β†’ Event Hubs β†’ Databricks β†’ Power BI), we use Azure Data Factory."

Interview tip: Don't just list features β€” frame your answer around the decision: "I chose Workflows over Airflow because our pipeline was 100% Databricks-native and we didn't need the operational overhead of maintaining a separate Airflow cluster."

What NOT to say: "Airflow is outdated" or "Workflows can do everything Airflow can." Airflow's multi-platform orchestration is a genuine strength that Workflows cannot match. Dismissing it shows you haven't worked with complex, multi-system environments.

Q2: How do tasks pass data to each other in a Workflow?

Simple Explanation: In a workflow with multiple tasks (bronze β†’ silver β†’ gold), sometimes Task 2 needs information from Task 1. For example, Task 1 ingests data and counts 1.5 million records. Task 2 needs to know that count to decide how to process the data.

Databricks provides dbutils.jobs.taskValues for this β€” Task 1 can SET values, and Task 2 can GET those values.

python β€” editable
# ============================================
# TASK 1: Bronze Ingestion
# ============================================
# After ingesting data, save useful info for downstream tasks
dbutils.jobs.taskValues.set(key="record_count", value=1500000)
# "I ingested 1.5 million records" β†’ save for Task 2

dbutils.jobs.taskValues.set(key="max_booking_date", value="2026-03-15")
# "The latest booking date in this batch is March 15" β†’ save for Task 2

dbutils.jobs.taskValues.set(key="status", value="success")
# "I completed successfully" β†’ save for Task 2


# ============================================
# TASK 2: Silver Transformation (runs AFTER Task 1)
# ============================================
# Read values that Task 1 saved
count = dbutils.jobs.taskValues.get(
    taskKey="bronze_ingestion",      # Name of the upstream task
    key="record_count"               # Which value to get
)
max_date = dbutils.jobs.taskValues.get(
    taskKey="bronze_ingestion",
    key="max_booking_date"
)

# Use the values for conditional logic
if count > 1000000:
    # Large batch β†’ use a different processing strategy
    process_large_batch()
else:
    process_normal_batch()

Interview tip: Mention that task values are lightweight metadata (strings, numbers), NOT large datasets. For passing actual data between tasks, use Delta tables. Task values are for control flow decisions like "how many records were ingested?" or "did upstream succeed?"

What NOT to say: "I'd write results to a shared file or global variable." That shows you don't know about the built-in mechanism. Also don't say "I'd pass DataFrames between tasks" β€” task values are for small metadata, not data payloads.

Answer First: A table-triggered job starts when an upstream table update satisfies its trigger, reducing polling waste and freshness delay. Event and run histories link the source change to the downstream execution.

Memory Map: table-triggered jobs -> table update event satisfies a job trigger -> dependency-aware scheduler starts downstream work -> run identity binds source version to execution -> event and run histories prove causality [DB_04_Production_CICD_MockInterview.md:110].

Q3: What are table-triggered jobs?

Simple Explanation: Normally, jobs run on a fixed schedule (e.g., "every day at 6am"). But what if bronze data arrives at unpredictable times? You'd either run too often (wasting compute) or too rarely (stale data).

Table-triggered jobs (new October 2025) solve this: the job runs AUTOMATICALLY when the source table gets new data. No cron schedule needed.

Real-world analogy: Instead of checking your mailbox every hour, you set up a notification: "Alert me when new mail arrives." That's table-triggered.

yaml
# In Declarative Automation Bundle config (databricks.yml):
resources:
  jobs:
    silver_bookings:
      trigger:
        table:
          condition: ANY_UPDATED          # Run when ANY of these tables change
          table_names:
            - travel_prod.bookings.bronze_bookings
            - travel_prod.bookings.bronze_cancellations
      # When bronze_bookings OR bronze_cancellations gets new data,
      # this job automatically runs to update the silver layer!
      tasks:
        - task_key: transform_silver
          notebook_task:
            notebook_path: ./notebooks/silver_bookings.py

Example use case: "Our Silver layer automatically refreshes whenever new Bronze data lands β€” no fixed schedule, no unnecessary runs, no stale data."

Interview tip: Table-triggered jobs are event-driven β€” contrast them with cron-based schedules. Mention that they reduce both latency (data is fresher) and cost (no wasted runs when no new data arrives).

What NOT to say: "I'd just schedule the job to run every 5 minutes to check for new data." That's polling, which wastes compute. Table-triggered is event-driven and more efficient.

SECTION 2: CI/CD WITH DECLARATIVE AUTOMATION BUNDLES (1 hour)

Q4: What are Declarative Automation Bundles?

Simple Explanation: Imagine you need to deploy the same Databricks pipeline to 3 environments: dev, staging, and prod. Without CI/CD, you'd manually create jobs, configure clusters, and set schedules in each environment β€” error-prone and tedious.

Declarative Automation Bundles (formerly called "Asset Bundles", renamed March 2026) let you define your entire Databricks setup as YAML files in Git. Same code, same config, deployed to any environment with one command.

Real-world analogy: Think of it like a recipe book. Instead of cooking from memory (manual setup), you follow the recipe (YAML file). Same recipe, same dish, whether you cook in the dev kitchen or the prod kitchen.

Why "Declarative"? Because you DECLARE what you want (jobs, schedules, clusters) and Databricks creates everything for you. You don't write imperative code saying "create cluster, wait, attach notebook, run..."

yaml
# databricks.yml β€” THE RECIPE for your entire pipeline setup
bundle:
  name: travelco_booking_pipeline     # Name of this bundle

workspace:
  host: https://adb-1234567890.azuredatabricks.net  # Default workspace

resources:
  jobs:
    daily_booking_etl:               # Job definition
      name: "Daily Booking ETL Pipeline"
      schedule:
        quartz_cron_expression: "0 0 6 * * ?"   # Run at 6:00 AM daily
        timezone_id: "UTC"
      tasks:
        - task_key: bronze_ingestion           # First task
          notebook_task:
            notebook_path: ./notebooks/bronze_ingestion.py  # Which notebook to run
          new_cluster:                          # Cluster config for this task
            spark_version: "18.1.x-scala2.13"   # Databricks Runtime version
            node_type_id: "Standard_DS3_v2"     # Azure VM type
            num_workers: 4                       # 4 worker VMs
            spark_conf:
              spark.databricks.photon.enabled: "true"  # Enable Photon for speed

        - task_key: silver_transform           # Second task
          depends_on:
            - task_key: bronze_ingestion       # Runs AFTER bronze completes
          notebook_task:
            notebook_path: ./notebooks/silver_transform.py

        - task_key: gold_aggregate             # Third task
          depends_on:
            - task_key: silver_transform       # Runs AFTER silver completes
          notebook_task:
            notebook_path: ./notebooks/gold_aggregate.py

# ENVIRONMENTS β€” same pipeline, different workspaces
targets:
  dev:                                          # Development environment
    workspace:
      host: https://dev-adb.azuredatabricks.net
  staging:                                      # Staging/testing environment
    workspace:
      host: https://staging-adb.azuredatabricks.net
  prod:                                         # Production environment
    workspace:
      host: https://prod-adb.azuredatabricks.net
    run_as:
      service_principal_name: "travelco-prod-sp"  # Prod runs as Service Principal (not a person)
bash
# DEPLOY with one command per environment:
databricks bundle validate --target dev      # Check config is valid
databricks bundle deploy --target dev        # Deploy to dev
databricks bundle deploy --target staging    # Deploy to staging (same code!)
databricks bundle deploy --target prod       # Deploy to production (same code!)
# Same pipeline definition β†’ consistent across ALL environments

Interview tip: Emphasize that DABs solve the "works on my machine" problem for data pipelines. The same YAML defines everything β€” no manual clicking in the UI, no environment drift.

What NOT to say: "We just use the Databricks UI to create jobs in each environment." That's the opposite of infrastructure-as-code and shows no CI/CD maturity. Also avoid confusing DABs with Terraform β€” DABs are Databricks-native, while Terraform is cloud-infrastructure-level.

Answer First: CI (Continuous Integration): Every time code is pushed to Git, automatically run tests and checks.

Memory Map: CI/CD work with Azure DevOps -> pull request starts Azure pipeline validation -> tests and policy checks gate deployment -> environment-specific target receives the bundle -> release history supports rollback [DB_04_Production_CICD_MockInterview.md:227].

Q5: How does CI/CD work with Azure DevOps?

Simple Explanation: CI/CD means:

  • CI (Continuous Integration): Every time code is pushed to Git, automatically run tests and checks
  • CD (Continuous Deployment): After tests pass, automatically deploy to staging/production
yaml
# azure-pipelines.yml β€” The CI/CD pipeline definition

trigger:
  branches:
    include: [main, develop]    # Run this pipeline when code is pushed to main or develop

pool:
  vmImage: 'ubuntu-latest'     # Run on Ubuntu (Azure-hosted build agent)

stages:
  # ============================================
  # STAGE 1: VALIDATE (run on every push)
  # ============================================
  - stage: Validate
    jobs:
      - job: ValidateAndTest
        steps:
          - task: UsePythonVersion@0
            inputs:
              versionSpec: '3.10'    # Use Python 3.10

          - script: |
              pip install databricks-cli ruff pytest
              ruff check src/              # Lint: check code style and errors
              pytest tests/ -v             # Unit tests: test transformation logic
            displayName: 'Lint and Unit Tests'

          - script: |
              databricks bundle validate --target staging
              # Validate: check that the YAML config is correct
            displayName: 'Validate Bundle Config'

  # ============================================
  # STAGE 2: DEPLOY TO STAGING (only on main branch)
  # ============================================
  - stage: DeployStaging
    dependsOn: Validate                  # Only runs after Validate passes
    condition: eq(variables['Build.SourceBranch'], 'refs/heads/main')
    # Only deploy when pushing to main branch (not feature branches)
    jobs:
      - job: DeployToStaging
        steps:
          - script: |
              databricks bundle deploy --target staging
              # Deploy the pipeline to staging environment
              databricks bundle run daily_booking_etl --target staging
              # Run the pipeline on staging data (integration test)
            displayName: 'Deploy and Integration Test on Staging'

  # ============================================
  # STAGE 3: DEPLOY TO PRODUCTION (with manual approval!)
  # ============================================
  - stage: DeployProd
    dependsOn: DeployStaging              # Only runs after staging succeeds
    jobs:
      - deployment: ProductionDeploy
        environment: 'production'         # REQUIRES manual approval by a reviewer!
        # Someone must click "Approve" in Azure DevOps before this runs
        strategy:
          runOnce:
            deploy:
              steps:
                - script: |
                    databricks bundle deploy --target prod
                  displayName: 'Deploy to Production'

Flow: Push code β†’ Auto-lint + test β†’ Auto-deploy staging β†’ Manual approval β†’ Deploy prod

Interview tip: Always mention the manual approval gate for production. It shows you understand that fully automated prod deploys are risky β€” a human reviewer should validate staging results before promoting to production.

What NOT to say: "We deploy straight to production after tests pass." No manual gate for prod deployments is a red flag in enterprise environments. Also don't say "We test in production" β€” that's what staging is for.

Answer First: Testing ensures your pipeline works correctly before deploying to production. There are 4 levels.

Memory Map: What testing strategies exist for Databricks pipelines -> unit tests isolate transformation logic -> integration tests exercise storage and services -> data assertions enforce pipeline contracts -> staged end-to-end runs gate promotion [DB_04_Production_CICD_MockInterview.md:312].

Q6: What testing strategies exist for Databricks pipelines?

Simple Explanation: Testing ensures your pipeline works correctly before deploying to production. There are 4 levels:

πŸ—‚οΈ1. UNIT TESTS (fastest β€” run in CI, no Spark needed)
What: Test your transformation logic with small sample data
How: pytest with pandas DataFrames (no Spark cluster needed!)
Example: "Does my dedup function keep only the latest record?"
Where: Runs in Azure DevOps build agent (free, fast)
New: pytest now integrated in Databricks workspace (Feb 2026)
2. INTEGRATION TESTS (medium β€” run on staging cluster)
What: Run the full pipeline end-to-end on sample data
How: Deploy to staging β†’ run β†’ check output matches expected results
Example: "Does Bronze β†’ Silver β†’ Gold produce the right output?"
Tool: Nutter framework (Databricks' own testing framework)
Check: Schema correct? Row counts match? Checksums match?
3. DATA QUALITY TESTS (continuous β€” run as part of pipeline)
What: Quality checks built into the pipeline itself
How: Lakeflow expectations (@dlt.expect, @dlt.expect_or_fail)
Example: "Are there any null booking_ids? Any negative fares?"
New: Quality monitoring anomaly detection (auto-alerts, Feb 2026)
4. PERFORMANCE TESTS (periodic β€” run before major releases)
What: Benchmark pipeline speed on production-scale data
How: Run on staging with a copy of production data
Check: Is the pipeline >20% slower than the previous version?
Alert: If performance regresses, block the release

Interview tip: When asked "How do you test notebooks?", don't just say "we run them and check the output." Show the 4-layer pyramid: unit (fast, cheap, no Spark) β†’ integration (staging cluster) β†’ quality (built into pipeline) β†’ performance (periodic benchmarks). Emphasize that unit tests run WITHOUT a Spark cluster β€” pure Python + pandas.

What NOT to say: "We don't test notebooks β€” we just promote them to prod and monitor." Also avoid "We only do manual testing" β€” that's not scalable for a team of 200+ engineers.

SECTION 3: COST MANAGEMENT (45 min)

Answer First: Databricks can get expensive fast β€” especially with 200+ engineers and hundreds of pipelines. Cost management is about: using the right compute for each workload, preventing waste, and tracking who spends what.

Memory Map: you manage costs for a large Databricks deployment -> usage tags attribute spend to owners -> cluster policies enforce size and idle termination -> job compute removes persistent capacity -> billing dashboards confirm savings [DB_04_Production_CICD_MockInterview.md:353].

Q7: How do you manage costs for a large Databricks deployment?

Simple Explanation: Databricks can get expensive fast β€” especially with 200+ engineers and hundreds of pipelines. Cost management is about: using the right compute for each workload, preventing waste, and tracking who spends what.

5 areas of cost management:

πŸ—‚οΈ1. COMPUTE OPTIMIZATION (biggest cost driver!)
Production jobs β†’ Job Clusters (NOT All-Purpose β€” saves 30-50%)
β”‚ Job Clusters are created for the job and destroyed after.
β”‚ All-Purpose stay running and cost money even when idle.
BI queries β†’ Serverless SQL Warehouses
β”‚ Auto-suspend when idle (zero cost at night).
β”‚ Auto-scale with query load.
Bursty/intermittent workloads β†’ Serverless compute
β”‚ Pay ONLY for execution time β€” up to 70% savings.
Long-running streaming β†’ Standard clusters + Spot VMs
β”‚ Use Spot VMs for workers (up to 90% cheaper! See Q8 below).
Instance Pools β†’ Shared pool of warm VMs
β”‚ Clusters start faster (VMs already running in the pool).
Right-size β†’ Check actual utilization
If your cluster uses only 20% CPU, downsize it!
2. CLUSTER POLICIES (enforce rules so people can't waste money)
Max nodes: "No cluster can have more than 20 workers" (prevents runaway costs)
Allowed VM sizes: "Only Standard_DS3 to DS5" (no accidentally picking expensive GPUs)
Auto-termination: "Must auto-stop after 60 min idle" (no overnight idle clusters)
Required tags: "Must tag with team + project" (for cost tracking)
Photon required for > 4 workers (more efficient = less cost)
Block All-Purpose in prod workspace (force Job Clusters)
3. STORAGE OPTIMIZATION
VACUUM regularly β†’ delete old files β†’ free ADLS storage
OPTIMIZE β†’ fewer files β†’ less I/O β†’ less compute cost for reads
Predictive Optimization β†’ does both automatically (UC managed tables)
Lifecycle policies β†’ archive raw Bronze data after 90 days
Zstd compression β†’ default since Runtime 16.0 (smaller files)
4. QUERY OPTIMIZATION (process less data = less cost)
Photon β†’ more queries per DBU (faster = less time = less cost)
Liquid Clustering β†’ queries scan less data (skips irrelevant files)
SQL Warehouse auto-suspend β†’ zero cost when not queried
Cache hot Gold tables β†’ avoid re-reading from storage
5. MONITORING & CHARGEBACK (track and allocate costs)
Tag everything: clusters, jobs, warehouses β†’ team, project, cost_center
Azure usage dashboards (new Mar 2026) β†’ see costs per team
Query tags for SQL warehouses (new Feb 2026) β†’ see costs per query group
Budget alerts β†’ "Alert when team X exceeds $5000/month"
Weekly review β†’ find unused clusters, oversized VMs
Chargeback β†’ each team/airline partner pays for their usage

Interview tip: Structure your answer around these 5 areas. Start with compute (biggest lever), then policies (prevention), then storage, query optimization, and monitoring. Give a specific dollar savings example: "By moving 50 production jobs from All-Purpose to Job Clusters, we saved 40% on compute β€” roughly $30K/month."

What NOT to say: "Just use the cheapest VM type for everything." Cost optimization is about matching the right compute to the right workload, not just picking the cheapest option. Also don't say "We don't track costs per team" β€” chargeback is essential at scale.

Answer First: Azure has spare VMs that nobody is using at the moment. Instead of letting them sit idle, Azure offers them at a huge discount (up to 90% off) β€” these are called Spot VMs (or Low-Priority VMs). The catch: Azure can take them back with 30 seconds notice if someone else needs them.

Memory Map: Spot VMs? How do they save money -> cloud reclaims discounted spot capacity on demand -> fault-tolerant tasks retry on replacement nodes -> mixed fleets protect critical driver roles -> interruption and cost metrics confirm savings [DB_04_Production_CICD_MockInterview.md:418].

Q8: What are Spot VMs? How do they save money?

Simple Explanation: Azure has spare VMs that nobody is using at the moment. Instead of letting them sit idle, Azure offers them at a huge discount (up to 90% off) β€” these are called Spot VMs (or Low-Priority VMs). The catch: Azure can take them back with 30 seconds notice if someone else needs them.

Databricks handles this gracefully β€” if a Spot worker is reclaimed, Spark retries the task on another worker. Your job doesn't fail.

πŸ—‚οΈOn-Demand (Standard) VMs:
Guaranteed β€” won't be taken away
Full price
Use for: driver node (must be reliable), critical streaming jobs
Spot (Low-Priority) VMs:
Up to 90% cheaper!
Can be evicted with 30-second notice
Databricks auto-retries tasks on remaining workers
Use for: worker nodes, batch processing, development
Best practice for production:
Driver: ALWAYS On-Demand (driver failing = entire job fails)
Workers: Mix of On-Demand (minimum) + Spot (auto-scale extra)
Example: min_workers=2 (on-demand), max_workers=20 (spot)
β†’ Guaranteed baseline of 2 workers + up to 18 cheap spot workers

Example answer: "Our batch ETL jobs use Spot VMs for 80% of workers, saving 60-70% on compute costs. The driver is always on-demand to ensure job reliability."

Interview tip: Always mention the driver/worker split β€” driver on-demand, workers spot. This shows you understand the failure modes: losing a worker is recoverable (Spark retries the task), but losing the driver kills the entire job.

What NOT to say: "Use Spot VMs for everything including the driver." The driver is a single point of failure β€” if it gets evicted, the entire job fails. Also don't say "Spot VMs are always available" β€” they can be reclaimed at any time, which is why you need a minimum number of on-demand workers.

SECTION 4: PRODUCTION DEBUGGING (45 min)

Answer First: Key rule: Read execution plans BOTTOM UP. The bottom is where data reading starts, and the top is the final result.

Memory Map: you read a Spark execution plan -> explain output traces parsed through physical plans -> exchanges identify shuffle boundaries -> join and scan operators expose cost drivers -> SQL UI statistics confirm optimizer estimates [DB_04_Production_CICD_MockInterview.md:454].

Q9: How do you read a Spark execution plan?

Simple Explanation: When you run a Spark query, Spark creates a plan β€” a step-by-step recipe for how to process the data. Reading this plan tells you: Is the query efficient? Are there unnecessary shuffles? Is Spark using the right join strategy?

Key rule: Read execution plans BOTTOM UP. The bottom is where data reading starts, and the top is the final result.

# Example query: Total fare by airport for a specific date
df = spark.table("bookings") \
.filter(col("booking_date") == "2026-03-15") \ # Filter by date
.join(spark.table("passengers"), "passenger_id") \ # Join with passengers
.groupBy("departure_airport").agg(sum("fare_amount")) # Sum fares by airport
df.explain(True) # Show the execution plan
== Physical Plan == (Read BOTTOM UP ↑)
*(3) HashAggregate(keys=[departure_airport], functions=[sum(fare_amount)])
β”‚ ↑ STEP 5: Final aggregation β€” compute the final SUM per airport
β”‚
+- Exchange hashpartitioning(departure_airport, 200)
β”‚ ↑ STEP 4: SHUFFLE β€” redistribute data by airport (expensive! stage boundary)
β”‚
+- *(2) HashAggregate(keys=[departure_airport], partial_sum)
β”‚ ↑ STEP 3: Partial aggregation β€” each worker computes a local SUM first
β”‚ (this reduces data before the shuffle β€” smart optimization)
β”‚
+- *(2) Project [departure_airport, fare_amount]
β”‚ ↑ STEP 2: Column pruning β€” keep only the columns we need
β”‚
+- *(2) BroadcastHashJoin [passenger_id]
β”‚ ↑ STEP 1b: JOIN β€” passengers table was BROADCAST (sent to all workers)
β”‚ BroadcastHashJoin = GOOD (no shuffle needed for this join)
β”‚ SortMergeJoin would be BAD (requires shuffle on both sides)
β”‚
:- *(2) Filter (booking_date = 2026-03-15)
: +- FileScan delta PushedFilters: [EqualTo(booking_date)]
: ↑ STEP 1a: Read bookings table with PREDICATE PUSHDOWN
: PushedFilters means the filter was pushed into file reading
:β†’Delta skips files that don't contain March 15 data
β”‚
+- BroadcastExchange
+- FileScan delta [passenger_id, departure_airport]
↑ STEP 0: Read passengers table (broadcast to all workers)

What to look for (red flags vs green flags):

ElementGood or Bad?What It Means
BroadcastHashJoinGoodSmall table sent to all workers β€” no shuffle needed
SortMergeJoinCheckBoth tables shuffled β€” can broadcast instead if one side is small?
ExchangeExpensiveSHUFFLE = data moving between workers. Fewer = better
PushedFiltersGoodFilter pushed to file level β€” Delta skips irrelevant files
No PushedFiltersBadFilter NOT pushed β€” reading more data than needed
WholeStageCodegen (*)GoodSpark compiled the query to fast native code
Missing WholeStageCodegenCheckMay be using Python UDFs that break codegen

Interview tip: When asked to read a plan, say "I read bottom-up" first. Then narrate each step: "It starts by scanning the Delta table with predicate pushdown, then broadcasts the smaller table for a hash join, then does a partial aggregate before shuffling." This shows structured thinking.

What NOT to say: "I just look at the overall time and add more workers if it's slow." Reading execution plans is a core senior DE skill. Also avoid reading the plan top-down β€” bottom-up is the correct direction.

Answer First: Spark UI is a web interface that shows you exactly what happened during your job β€” how long each task took, how much data was shuffled, whether any tasks were much slower than others (data skew).

Memory Map: you debug slow jobs using Spark UI -> SQL or job run opens its stage timeline -> slow tasks identify skew spill or bad operators -> input and exchange counters narrow the cause -> comparable rerun validates the fix [DB_04_Production_CICD_MockInterview.md:520].

Q10: How do you debug slow jobs using Spark UI?

Simple Explanation: Spark UI is a web interface that shows you exactly what happened during your job β€” how long each task took, how much data was shuffled, whether any tasks were much slower than others (data skew).

πŸ—‚οΈSPARK UI DEBUGGING CHECKLIST:
(Follow this step-by-step when a job is slow)
1. JOBS TAB β†’ Which job is slow?
Find the job with the longest duration
Click on it to see its stages
2. STAGES TAB β†’ Find the bottleneck stage
Sort by Duration β†’ which stage takes the longest?
Check Shuffle Read/Write Size β†’ large shuffle = expensive
Check Spill (Memory) β†’ if > 0, workers ran out of memory
and had to write to disk (very slow!)
3. TASKS TAB β†’ Find slow tasks (within the bottleneck stage)
Sort by Duration β†’ any outliers?
Max Task Duration vs Median Duration:
β”‚ If max = 40 minutes, median = 30 seconds β†’ DATA SKEW!
β”‚ One task is processing way more data than others
Shuffle Read per task β†’ should be roughly EQUAL
β”‚ If one task reads 50 GB and others read 100 MB β†’ SKEW
GC Time β†’ if > 10% of task time β†’ memory pressure (need more RAM)
4. SQL TAB β†’ Check the execution plan
Is the join strategy correct? (Broadcast vs SortMerge)
Rows output at each node β†’ data amplification?
Time per operator β†’ which step is slowest?
5. EXECUTORS TAB β†’ Check worker health
Any dead executors? (might have OOM'd)
Task count per executor β†’ balanced?
Memory usage β†’ anyone maxed out?

Interview tip: Walk through this checklist when asked "How would you debug a slow job?" β€” it shows systematic thinking, not guessing. Mention specific metrics: "I'd check the max task duration vs median β€” if there's a 100x difference, that's data skew."

What NOT to say: "I'd just add more workers." Throwing hardware at a skew problem doesn't fix it β€” the slow task still runs on one node. Also don't say "I'd look at the logs" without specifying which Spark UI tab and what metrics you're checking.

Answer First: This is one of the most common interview scenarios. The key: DON'T just say "run OPTIMIZE". Show a systematic investigation process.

Memory Map: Scenario β€” Pipeline suddenly 5x slower, no code change. What happened -> unchanged code narrows investigation to inputs and environment -> run comparison exposes volume, skew, or dependency drift -> targeted rollback or tuning addresses cause -> restored SLA confirms diagnosis [DB_04_Production_CICD_MockInterview.md:565].

Q11: Scenario β€” Pipeline suddenly 5x slower, no code change. What happened?

Simple Explanation: This is one of the most common interview scenarios. The key: DON'T just say "run OPTIMIZE". Show a systematic investigation process.

πŸ—‚οΈINVESTIGATION CHECKLIST (go through these one by one):
1. DATA VOLUME β€” Did the source data suddenly get bigger?
Holiday season? Black Friday? End of month?
Check: Compare input sizes today vs last week
Fix: If data doubled, you may need more workers or partitions
2. DATA SKEW β€” Is one partition much bigger than others?
New popular flight route? One airline sent 10x more data?
Check: Spark UI β†’ Tasks tab β†’ one task taking 100x longer?
Fix: Salting, broadcast join, AQE skew join handling
3. SMALL FILES ACCUMULATED β€” 6 months without OPTIMIZE?
Streaming writes create many small files over time
Check: DESCRIBE DETAIL my_table β†’ numFiles (if > 10,000 = problem!)
Fix: OPTIMIZE my_table; + enable auto-compaction
4. STALE STATISTICS β€” Spark using wrong join strategy?
Table grew past broadcast threshold β†’ Spark switched to slow SortMergeJoin
Check: df.explain(True) β†’ is it using SortMergeJoin when BroadcastHashJoin is better?
Fix: ANALYZE TABLE my_table COMPUTE STATISTICS
5. CLUSTER CHANGES β€” Did someone change the cluster config?
Fewer workers? Different VM type? Autoscale range changed?
Check: Cluster config history in workspace
6. INFRASTRUCTURE β€” Cloud platform issues?
ADLS Gen2 throttling? Network latency? Azure outage?
Check: Azure Monitor for storage latency spikes
7. UPSTREAM CHANGES β€” Did the source data structure change?
New columns causing schema evolution? Different data distribution?
Check: Compare source data schema and patterns
8. DELTA TABLE HEALTH:
DESCRIBE DETAIL bookings;
-- numFiles: if > 10,000 β†’ small file problem β†’ OPTIMIZE
-- sizeInBytes: unexpected growth? β†’ data volume spike
QUICK FIXES (after diagnosing the root cause):
OPTIMIZE all affected tables (compacts small files)
ANALYZE TABLE bookings COMPUTE STATISTICS (refresh stats)
Increase spark.sql.shuffle.partitions (if data grew)
Enable AQE if not already (handles skew and partitions automatically)
Consider Liquid Clustering (for long-term health)

Interview tip: Start with "I'd investigate systematically rather than guessing." Walk through 2-3 of these checks with specific commands. End with: "Once I identify the root cause, I'd fix it AND add monitoring to catch it earlier next time." That last part shows senior-level thinking β€” prevention, not just reaction.

What NOT to say: "I'd just run OPTIMIZE and hope it fixes it." That's a guess, not a diagnosis. Also don't say "I'd restart the cluster" as your first step β€” that's treating the symptom, not finding the cause.

Answer First: Production debugging begins with the failed task and first causal error, then correlates Spark UI, logs, data volume, infrastructure events, and recent changes. Apply one safe change and verify recovery with the same evidence.

Memory Map: Common production issues β€” quick reference -> symptom directs the first evidence source -> logs plans and metrics distinguish common causes -> bounded correction addresses that cause -> operational check closes the incident [DB_04_Production_CICD_MockInterview.md:624].

Q12: Common production issues β€” quick reference

IssueWhat You SeeRoot CauseHow to Fix
ConcurrentModificationExceptionTwo pipelines write to same table at same timeConcurrent writes touch same filesPartition writes by time, or use WAP (Write-Audit-Publish) pattern
Pipeline silently produces wrong dataReports show wrong numbersNo data quality checksAdd Lakeflow expectations at every layer
Costs spike on weekendsBill jumps 2x on Saturday/SundayDevelopers leave clusters runningEnforce auto-termination in cluster policies
Query suddenly slowQuery that took 30 sec now takes 10 minTable statistics are outdatedANALYZE TABLE bookings COMPUTE STATISTICS
Streaming job keeps restartingJob restarts every few minutesCheckpoint/state store corruptedDelete checkpoint, reprocess from source
SchemaEvolutionExceptionPipeline fails with schema errorSource added new columnsUse Auto Loader rescue mode
FileNotFoundExceptionQuery fails with "file not found"VACUUM deleted a file while query was reading itIncrease VACUUM retention period (default 7 days)
Job works in dev, fails in prodSame code, different resultsDifferent cluster config/runtimeUse Declarative Automation Bundles (same config everywhere)

Interview tip: Memorize 3-4 of these issue/fix pairs. When asked "Tell me about a production issue you debugged," pick one and walk through it using the STAR method (Situation, Task, Action, Result).

What NOT to say: "We never have production issues." Every team does. The interviewer wants to see how you handle them, not hear that you're perfect.

SECTION 5: SYSTEM DESIGN (45 min)

Answer First: A scalable Databricks platform separates environments and team ownership, centralizes Unity Catalog governance, standardizes compute and deployment, and measures reliability and cost. The design should make those boundaries and operating controls explicit.

Memory Map: a data platform for a travel platform on Azure Databricks -> data platform for travel platform on azure databricks establishes one architecture constraint before component selection -> source guarantees and scale set constraints -> state and governance boundaries shape processing -> recovery and serving paths meet the SLA -> lineage and cost evidence validate design [DB_04_Production_CICD_MockInterview.md:645].

Q13: Design a data platform for a travel platform on Azure Databricks

Simple Explanation: This is a "big picture" architecture question. They want to see: Can you design an entire data platform? Think about workspaces, catalogs, compute, governance, CI/CD, cost, and monitoring.

πŸ—‚οΈ============================================================
TRAVEL PLATFORM DATA ARCHITECTURE β€” AZURE DATABRICKS ARCHITECTURE
WORKSPACE STRATEGY (separate environments):
travel-dev β†’ Development & experimentation
β”‚ All engineers have full access. Sandbox for testing.
travel-staging β†’ Testing & validation
β”‚ Integration tests run here before promoting to prod.
travel-prod β†’ Production workloads
β”‚ Only Service Principals write. Humans have read-only access.
travel-analytics β†’ Ad-hoc queries & BI
β”‚ Business analysts connect Power BI here.
ALL workspaces β†’ Connected to the SAME Unity Catalog metastore
So table definitions, permissions, and lineage are shared.
CATALOG STRUCTURE (how data is organized in Unity Catalog):
travel_raw β†’ Bronze layer (all domains)
β”‚ Raw data from all sources, append-only
travel_curated β†’ Silver layer (organized by domain)
bookings schema β†’ clean booking facts + dimensions
passengers schema β†’ passenger SCD Type 2 dimensions
flights schema β†’ flight schedules, routes
travel_analytics β†’ Gold layer (cross-domain aggregations)
β”‚ Daily revenue, route performance, passenger 360
travel_ml β†’ ML assets
β”‚ Feature tables, registered models, experiments
travel_sandbox β†’ Developer experimentation
Auto-cleaned after 30 days. No production data.
COMPUTE STRATEGY (right compute for each workload):
File ingestion: Serverless (auto-scales with file volume)
Silver MERGE: Job Clusters + Photon (heavy transformations)
Gold aggregations: Serverless SQL Warehouse (BI queries)
ML training: GPU clusters (separate from ETL)
Development: Serverless notebooks (instant start, no idle cost)
24/7 streaming: Standard clusters + Spot workers (cost-efficient)
DATA GOVERNANCE (who can access what):
Unity Catalog β†’ centralized access control
ABAC β†’ tag PII tables β†’ one policy governs all
Row-level security β†’ each airline sees only their own data
Column masking β†’ passenger PII hidden from non-authorized teams
GDPR deletion pipeline β†’ automated with compliance logging
Auto-classification β†’ auto-detect PII columns
Delta Sharing β†’ share data with airline partners (no copying)
CI/CD (how code gets to production):
Azure DevOps pipelines β†’ automate lint, test, deploy
Declarative Automation Bundles β†’ YAML configs in Git
Environment promotion: dev β†’ staging β†’ prod
Testing: unit (pytest) β†’ integration (Nutter) β†’ quality (expectations)
Service Principals for prod β†’ no human credentials in production
COST GOVERNANCE (prevent waste):
Cluster policies β†’ enforce max size, auto-termination, required tags
Tags β†’ team, project, airline_partner, cost_center
Serverless for bursty β†’ Job Clusters for scheduled β†’ Spot for batch
Azure cost dashboards + budget alerts per team
Monthly review β†’ unused clusters, oversized VMs
MONITORING (is everything working?):
Lakeflow expectations β†’ data quality at every layer
Quality monitoring anomaly detection β†’ auto-alerts
Pipeline SLAs: Bronze < 5 min, Silver < 30 min, Gold < 1 hour
Azure Monitor β†’ infrastructure health
PagerDuty β†’ critical failure alerts
Weekly data health review β†’ team syncs on data quality

Interview tip: Structure your design around 6-7 pillars (workspace, catalog, compute, governance, CI/CD, cost, monitoring). Don't try to cover everything in detail β€” hit all pillars at a high level, then go deep on whichever the interviewer asks about.

What NOT to say: "I'd put everything in one workspace with one cluster." That shows no understanding of environment separation, cost isolation, or security boundaries. Also don't skip governance β€” in enterprise environments, governance is not optional.

SECTION 6: MOCK INTERVIEW β€” 10 Most Likely Scenario Questions (1 hour)

Practice answering each in 3-5 minutes OUT LOUD. Time yourself.

Answer First: Example: "In my current role, I work with Delta Lake tables processing X million records daily. I've implemented Medallion architecture using Lakeflow Pipelines with quality expectations at every layer. A key challenge was optimizing a MERGE operation on our fact table that took 3 hours β€” by adding partition pruning, OPTIMIZE with Z-ORDER, and switching to Photon, we brought it down to 40 minutes."

Memory Map: "Tell me about your experience with Databricks and Delta Lake." -> experience answer anchors one owned workload -> architecture choices connect Delta and Databricks capabilities -> measurable incident or optimization shows judgment -> outcome quantifies production impact [DB_04_Production_CICD_MockInterview.md:733].

MOCK Q1: "Tell me about your experience with Databricks and Delta Lake."

How to answer (structure):

  1. Start with your role and the scale you work at
  2. Mention specific Databricks features you've used
  3. Share ONE challenge you solved (with results)
  4. Keep it to 2-3 minutes

Example: "In my current role, I work with Delta Lake tables processing X million records daily. I've implemented Medallion architecture using Lakeflow Pipelines with quality expectations at every layer. A key challenge was optimizing a MERGE operation on our fact table that took 3 hours β€” by adding partition pruning, OPTIMIZE with Z-ORDER, and switching to Photon, we brought it down to 40 minutes."

Interview tip: Quantify everything β€” records per day, table sizes, time savings, cost reductions. "We process data" is weak; "We process 500M records daily across 200 Delta tables" is strong.

What NOT to say: "I've used Databricks for basic notebook development." That's a junior answer. Also don't list every feature without context β€” "I've used Delta Lake, Unity Catalog, Workflows, Photon..." sounds like you read a marketing page. Instead, anchor each feature to a problem you solved.

Answer First: Source: Debezium Oracle connector reads redo logs (no Oracle performance impact)

Memory Map: "Design a CDC pipeline from Oracle to Delta Lake for our booking system." -> Oracle redo position orders captured mutations -> connector publishes keyed envelopes to Kafka -> streaming consumer merges idempotently into Delta -> offset and row reconciliation prove completeness [DB_04_Production_CICD_MockInterview.md:749].

MOCK Q2: "Design a CDC pipeline from Oracle to Delta Lake for our booking system."

Key points to cover (in order):

  1. Source: Debezium Oracle connector reads redo logs (no Oracle performance impact)
  2. Message broker: Azure Event Hubs (Kafka-compatible, managed service)
  3. Bronze: Auto Loader ingests from Event Hubs, appends raw CDC events as-is
  4. Silver: MERGE for fact tables (SCD Type 1), apply_changes for dimensions (SCD Type 2)
  5. Gold: Materialized views for business aggregations
  6. Quality: Lakeflow expectations at every layer
  7. Governance: Unity Catalog from day 1, PII masking on passenger data
  8. GDPR: Deletion pipeline for "right to be forgotten"

Interview tip: Draw the pipeline left-to-right: Source β†’ Broker β†’ Bronze β†’ Silver β†’ Gold. At each stage, mention the specific tool and WHY you chose it. End with governance and monitoring β€” most candidates forget these.

What NOT to say: "I'd do a full table dump from Oracle every night." That's not CDC β€” that's batch extraction. CDC is about capturing incremental changes from the transaction log in near-real-time. Also don't skip the message broker β€” going directly from Oracle to Databricks creates tight coupling.

Answer First: SQL MERGE with merge key trick β€” show you can write the code manually (Day 2, Q5).

Memory Map: "How would you implement SCD Type 2 for our passenger dimension?" -> staging query ranks passenger changes by business key -> current version closes on a deterministic match -> successor version enters with a new interval -> temporal assertions prevent overlap [DB_04_Production_CICD_MockInterview.md:767].

MOCK Q3: "How would you implement SCD Type 2 for our passenger dimension?"

Cover BOTH approaches:

  1. SQL MERGE with merge_key trick β€” show you can write the code manually (Day 2, Q5)
  2. Lakeflow apply_changes β€” show you know the modern, simpler approach (Day 2, Q6)
  3. Mention hash-based change detection (MD5 of tracked columns)
  4. Mention deduplication of source before MERGE

Interview tip: Start with the MERGE approach to show you understand the internals, then say "In practice, I'd use Lakeflow apply_changes because it handles all the edge cases automatically β€” but it's important to understand what it does under the hood." This shows depth AND pragmatism.

What NOT to say: "I'd just overwrite the old record with the new one." That's SCD Type 1, not Type 2. Type 2 preserves history β€” you close the old record (set end_date, is_current=false) and insert a new one. Also don't say "I'd use a slowly changing dimension library" without being able to explain the MERGE logic yourself.

πŸ’‘ Interview Tip
Delta integration summary β€” MOCK Q4: "Our booking fact table MERGE takes 3 hours. How would you optimize?": Databricks jobs invoke Delta MERGE, but scan pruning, source deduplication, file layout, and operation-metric diagnosis belong to the canonical Delta optimization answer. Open the canonical Delta answer.

Answer First: Unity Catalog β†’ centralized governance, 3-level namespace.

Memory Map: "How do you handle data governance and GDPR for passenger data?" -> handle data governance and gdpr for passenger data selects the governed subject-data workflow -> verified request identifies the subject -> lineage discovers governed copies -> owners coordinate erasure and exports -> completion evidence closes the case [DB_04_Production_CICD_MockInterview.md:799].

MOCK Q5: "How do you handle data governance and GDPR for passenger data?"

Cover these areas:

  1. Unity Catalog β†’ centralized governance, 3-level namespace
  2. Column masking β†’ PII (email, phone) masked for non-authorized users
  3. Row-level security β†’ each airline partner sees only their own bookings
  4. ABAC β†’ tag-based policies (new!) β€” one policy governs all PII tables
  5. GDPR erasure workflow β†’ Unity Catalog discovery + owner coordination + compliance evidence; Delta owns physical purge and retention safety
  6. Pseudonymization β†’ alternative to full deletion (preserves analytics)
  7. Audit β†’ Unity Catalog audit logs β†’ Azure Monitor
  8. Auto-classification β†’ auto-detect PII columns (new 2025)

Interview tip: Frame GDPR as a technical AND process challenge. Unity Catalog discovery, accountable owners, downstream and backup handling, and audit proof are the Databricks responsibilities; link the physical table-erasure procedure to its Delta owner.

What NOT to say: "We just delete the records." Erasure spans governed tables, exports, backups, downstream products, approvals, and regulator-facing evidence.

MOCK Q6: "Explain your CI/CD approach for Databricks pipelines."

Cover:

  1. Declarative Automation Bundles β†’ YAML config in Git (same config for all environments)
  2. Azure DevOps pipeline β†’ lint (ruff) β†’ unit test (pytest) β†’ validate bundle β†’ deploy staging β†’ integration test β†’ manual approval β†’ deploy prod
  3. Three environments: dev, staging, prod (same Unity Catalog metastore)
  4. Service Principals β†’ production runs under robot accounts, not human accounts
  5. Testing: unit (pandas), integration (Nutter), quality (Lakeflow expectations)
  6. Rollback: redeploy previous bundle version from Git

Interview tip: Mention the rollback strategy β€” most candidates forget this. "If a production deployment introduces a bug, we redeploy the previous Git commit using databricks bundle deploy --target prod from the last known good commit. For data recovery, we use Delta Time Travel to RESTORE tables to the pre-bug version."

What NOT to say: "We manually deploy notebooks by clicking through the UI." That's the opposite of CI/CD. Also don't say "We use one environment for everything" β€” environment separation is fundamental.

Answer First: Cover all 5 areas (Day 4, Q7).

Memory Map: "How would you manage costs for a large Databricks deployment?" -> tagged cost baseline groups spend by workload -> right-sized job and serverless compute remove idle waste -> policies require termination and ownership -> monthly billing trend validates reduction [DB_04_Production_CICD_MockInterview.md:833].

MOCK Q7: "How would you manage costs for a large Databricks deployment?"

Cover all 5 areas (Day 4, Q7):

  1. Compute: Job Clusters for prod (not All-Purpose!), Serverless for bursty, Spot VMs for workers
  2. Cluster policies: enforce max size, auto-termination, required tags
  3. Storage: OPTIMIZE + VACUUM + Predictive Optimization
  4. Query: Photon + Liquid Clustering (scan less data)
  5. Monitoring: tags on everything, dashboards, budget alerts, chargeback per team

Interview tip: Lead with the biggest impact item: "The single biggest cost savings is switching production jobs from All-Purpose to Job Clusters β€” that alone saves 30-50% because All-Purpose clusters have a higher DBU rate and stay running even when idle."

What NOT to say: "I haven't really managed costs β€” that's the finance team's job." Cost management is a core DE responsibility. Also don't say "Turn off everything at night" β€” that works for interactive clusters but not for batch jobs that need to run overnight.

Answer First: Centralized governance for ALL data assets (tables, models, files).

Memory Map: "What is Unity Catalog and how would you structure it for our organization?" -> enterprise catalogs separate domains or environments -> schemas group owned data products -> groups receive privileges at the narrowest useful level -> lineage and audit review confirm the model [DB_04_Production_CICD_MockInterview.md:848].

MOCK Q8: "What is Unity Catalog and how would you structure it for our organization?"

Cover:

  1. Centralized governance for ALL data assets (tables, models, files)
  2. Three-level namespace: catalog.schema.table
  3. Catalog strategy: travel_prod, travel_dev, travel_staging, travel_sandbox
  4. Schema per domain: bookings, passengers, flights, analytics
  5. Access: row-level security per airline, column masking for PII, ABAC for scale
  6. Migration from Hive Metastore using UCX tool
  7. Delta Sharing for airline partner data access

Interview tip: Draw the hierarchy: metastore β†’ catalog β†’ schema β†’ table. Then explain that permissions cascade β€” granting SELECT on a catalog gives access to ALL schemas and tables within it, so you need to be intentional about your catalog structure.

What NOT to say: "Put everything in one catalog with one schema." That's a flat structure with no isolation or governance boundaries. Also don't confuse Unity Catalog with Hive Metastore β€” UC is the next generation with centralized governance, lineage, and cross-workspace support.

Answer First: Compare the slow run with its baseline across input volume, skew, scan bytes, shuffle, spills, and infrastructure time. The first materially changed metric identifies the investigation branch.

Memory Map: "A pipeline that was fine for 6 months is suddenly 5x slower. Walk me through your debugging." -> run comparison checks volume plan and infrastructure -> longest changed phase narrows the branch -> targeted repair addresses skew I O or capacity -> restored duration confirms diagnosis [DB_04_Production_CICD_MockInterview.md:865].

MOCK Q9: "A pipeline that was fine for 6 months is suddenly 5x slower. Walk me through your debugging."

Show systematic investigation (Day 4, Q11):

  1. Check data volume β€” did it spike? (seasonal, backfill?)
  2. Check Spark UI β†’ Tasks tab β†’ one task much slower than others? β†’ DATA SKEW
  3. Check table health: DESCRIBE DETAIL β†’ too many small files?
  4. Check if OPTIMIZE has been running regularly
  5. Check for upstream schema changes
  6. Check cluster config changes
  7. Quick fixes: OPTIMIZE, ANALYZE, enable AQE, increase shuffle partitions

Interview tip: End with prevention: "After fixing the immediate issue, I'd add monitoring to catch this earlier β€” set up alerts for when job duration exceeds 2x the historical average, and add a scheduled OPTIMIZE job to prevent small file accumulation."

What NOT to say: "I'd restart the cluster and re-run the job." That's a hope-based strategy. If the root cause is data skew or small files, restarting changes nothing. Also avoid blaming infrastructure first β€” 90% of the time the issue is data-related, not infrastructure.

Answer First: Pick 3-4 features you can speak about confidently.

Memory Map: "What's new in Databricks in 2025-2026 that excites you?" -> current feature selection starts from a real limitation -> new capability changes architecture or operations -> adoption risks constrain enthusiasm -> small evaluation supplies credible evidence [DB_04_Production_CICD_MockInterview.md:882].

MOCK Q10: "What's new in Databricks in 2025-2026 that excites you?"

Pick 3-4 features you can speak about confidently:

  1. Lakeflow Declarative Pipelines β€” DLT rebranded, contributed to open-source Apache Spark. Shows Databricks' commitment to open source.
  2. ABAC β€” tag-based governance at scale. One policy instead of 500 GRANTs.
  3. Serverless Workspaces β€” instant startup, pay-per-use. Changes how teams get started.
  4. Predictive Optimization β€” automatic OPTIMIZE/VACUUM. No more manual maintenance.
  5. Multi-table Transactions β€” atomic operations across tables. Solves real consistency problems.
  6. Lakebase β€” serverless Postgres for low-latency app serving inside Databricks.

Interview tip: Pick features that connect to real problems you've faced. "I'm excited about Predictive Optimization because we currently spend engineering time scheduling OPTIMIZE and VACUUM jobs β€” having Databricks handle this automatically frees us to focus on business logic."

What NOT to say: "I haven't been keeping up with new features." That signals you're not invested in the platform. Also avoid mentioning features you can't explain β€” if you say "Lakebase" but can't describe what it does, it backfires.

MOCK INTERVIEW β€” 10 Questions (Production & CI/CD Focus)

Instructions: These questions focus specifically on production operations, CI/CD, cost management, and debugging. Answer each in 3-5 minutes with the structure: Simple Explanation β†’ Technical Depth β†’ Specific Example.

MOCK PROD Q1: "Walk me through how you'd set up CI/CD for a Databricks project."

Simple Explanation: CI/CD for Databricks means automating the entire path from code commit to production deployment. Instead of manually creating jobs in the UI, everything is defined as code, tested automatically, and promoted through environments with gates.

Technical Depth:

πŸ—‚οΈSTEP 1: PROJECT STRUCTURE (in Git)
databricks.yml β†’ Declarative Automation Bundle config
src/
notebooks/ β†’ Pipeline notebooks (bronze, silver, gold)
lib/ β†’ Shared Python modules (transformations, utils)
tests/
unit/ β†’ pytest tests (no Spark needed)
integration/ β†’ Nutter tests (run on staging cluster)
azure-pipelines.yml β†’ CI/CD pipeline definition
requirements.txt β†’ Python dependencies
STEP 2: CI PIPELINE (triggered on every push)
Install dependencies (pip install ruff pytest databricks-cli)
Lint code (ruff check src/)
Run unit tests (pytest tests/unit/ -v)
β”‚ β†’ Test transformation functions with pandas DataFrames
β”‚ β†’ No Spark cluster needed = fast + free
Validate bundle (databricks bundle validate --target staging)
β”‚ β†’ Catch YAML syntax errors before deployment
Artifact: validated code ready for deployment
STEP 3: CD TO STAGING (triggered on merge to main)
databricks bundle deploy --target staging
databricks bundle run daily_etl --target staging
β”‚ β†’ Run full pipeline on staging data
Verify: check row counts, schema, data quality
Generate test report
STEP 4: CD TO PRODUCTION (manual approval gate)
Reviewer checks staging results in Azure DevOps
Click "Approve" β†’ triggers prod deployment
databricks bundle deploy --target prod
Smoke test: verify first run succeeds
Rollback plan: redeploy previous Git commit if issues

Key design decisions:

  • Service Principals: Prod deployments use a robot account, never a human's credentials
  • Environment parity: Same YAML, different targets β€” dev/staging/prod use identical pipeline definitions
  • Secrets: Stored in Azure Key Vault, referenced via Databricks secret scopes (never in Git)
  • Branch strategy: Feature branches β†’ PR β†’ merge to main β†’ auto-deploy staging β†’ manual approve β†’ prod

Interview tip: Walk through the pipeline stages in order. Mention specific tools at each stage (ruff for linting, pytest for unit tests, Nutter for integration, DABs for deployment). End with the rollback strategy β€” it shows you've thought about what happens when things go wrong.

What NOT to say: "We just push notebooks to the workspace using the UI." That's the absence of CI/CD. Also don't say "We use Jenkins" without explaining why β€” if you're on Azure, Azure DevOps or GitHub Actions are the natural choices. And never say "We skip testing to move faster" β€” that's a red flag for any production environment.

Answer First: Production failure investigation follows a systematic triage process. The goal is: understand the failure β†’ fix it β†’ prevent recurrence. Don't guess β€” follow the evidence.

Memory Map: "Your nightly ETL job failed at 3 AM. Walk me through your investigation." -> alert establishes failure time and blast radius -> run logs locate the first causal error -> safe retry or rollback restores service -> follow-up action prevents recurrence [DB_04_Production_CICD_MockInterview.md:960].

MOCK PROD Q2: "Your nightly ETL job failed at 3 AM. Walk me through your investigation."

Simple Explanation: Production failure investigation follows a systematic triage process. The goal is: understand the failure β†’ fix it β†’ prevent recurrence. Don't guess β€” follow the evidence.

Technical Depth:

πŸ—‚οΈMINUTE 0-5: TRIAGE (understand the blast radius)
Check alerting: Which task in the workflow failed?
β”‚ β†’ Databricks Workflows UI shows exactly which task failed (green/red)
Is this the first failure or recurring?
β”‚ β†’ Check job run history: one-off or 3 nights in a row?
What's the downstream impact?
β”‚ β†’ Are Gold tables stale? Are dashboards showing yesterday's data?
Communicate: Notify stakeholders "ETL delayed, investigating"
MINUTE 5-15: DIAGNOSE (find the root cause)
Read the error message carefully:
SparkException: Job aborted β†’ check Spark UI for task failures
OutOfMemoryError β†’ data volume spike or skew
FileNotFoundException β†’ VACUUM deleted files mid-read
ConcurrentModificationException β†’ two jobs writing same table
SchemaEvolutionException β†’ source schema changed
Timeout β†’ job exceeded max runtime
Check Spark UI (for the failed run):
Stages tab β†’ which stage failed?
Tasks tab β†’ did one task OOM while others were fine? (skew)
Executor tab β†’ any dead executors?
Check driver logs:
Search for "ERROR" or "Exception"
Look for the FIRST error (later errors are often cascading)
Check upstream data:
Did source data arrive? (check Bronze table timestamps)
Did data volume spike? (compare to last 7 days)
Did schema change? (new columns, renamed fields)
Check infrastructure:
Cluster started successfully?
ADLS Gen2 accessible? (network/auth issues)
Azure service health dashboard
MINUTE 15-30: FIX (get production back on track)
Option A: Re-run with "Repair Run" (re-runs ONLY failed tasks)
β”‚ β†’ Best option if it was a transient failure (network blip, spot VM eviction)
Option B: Fix the root cause and re-run
β”‚ β†’ Schema change? Update Auto Loader config
β”‚ β†’ Data skew? Add salting or repartition
β”‚ β†’ OOM? Increase cluster size or optimize the query
Option C: Skip the failed task and run downstream
β”‚ β†’ Only if the failed task is non-critical (e.g., a reporting table)
Verify: Check output table row counts and data quality after the fix
POST-MORTEM (same day):
Document: What failed, why, how it was fixed
Prevent: Add monitoring/alerting to catch this earlier
Alert if data volume > 2x historical average
Alert if job duration > 2x historical average
Add data quality expectations if they were missing
Automate: Can the fix be automated for next time?
Share: Brief the team on what happened and what changed

Interview tip: Structure your answer as Triage β†’ Diagnose β†’ Fix β†’ Prevent. The "Prevent" step is what separates senior from mid-level β€” anyone can fix a bug, but seniors build systems that prevent recurrence. Mention specific tools: "I'd check the Workflows UI to see which task failed, then look at the Spark UI stages tab for the failed task, then read the driver logs for the first ERROR."

What NOT to say: "I'd just re-run the job." Re-running without understanding why it failed means it'll probably fail again tomorrow. Also don't say "I'd wait until morning to investigate" β€” a 3 AM failure affecting production dashboards needs immediate attention (or at minimum, an automated retry with alerting).

Answer First: Cost management is a continuous discipline with five pillars: right-sizing compute, enforcing policies, optimizing storage, tuning queries, and tracking spend. The biggest lever is compute β€” it's typically 70-80% of the Databricks bill.

Memory Map: "How do you manage costs in a Databricks environment?" -> tags allocate spend to teams and workloads -> policies cap size and enforce termination -> job or serverless capacity removes idle clusters -> monthly cost and SLA trends balance savings [DB_04_Production_CICD_MockInterview.md:1032].

MOCK PROD Q3: "How do you manage costs in a Databricks environment?"

Simple Explanation: Cost management is a continuous discipline with five pillars: right-sizing compute, enforcing policies, optimizing storage, tuning queries, and tracking spend. The biggest lever is compute β€” it's typically 70-80% of the Databricks bill.

Technical Depth:

πŸ—‚οΈPILLAR 1: COMPUTE RIGHT-SIZING (70-80% of spend)
Production batch jobs β†’ Job Clusters (NOT All-Purpose)
β”‚ β†’ Job Clusters auto-terminate when the job finishes
β”‚ β†’ All-Purpose has higher DBU rate AND stays running when idle
β”‚ β†’ Switching saves 30-50% immediately
BI/SQL queries β†’ Serverless SQL Warehouse
β”‚ β†’ Auto-suspends after idle period (zero cost at night)
β”‚ β†’ Auto-scales with query concurrency
Bursty workloads β†’ Serverless compute
β”‚ β†’ Pay only for execution seconds, not idle time
Batch workers β†’ Spot VMs (up to 90% discount)
β”‚ β†’ Driver always on-demand, workers on Spot
β”‚ β†’ min_workers=2 (on-demand), max_workers=20 (spot)
Right-size existing clusters:
β†’ Check CPU/memory utilization via Ganglia metrics
β†’ If utilization < 30%, downsize the VM type or reduce workers
PILLAR 2: CLUSTER POLICIES (prevent waste before it happens)
Max worker count: cap at 20 for dev, 50 for prod
Auto-termination: mandatory 60-min idle timeout
Allowed VM types: whitelist Standard_DS3 to DS5 only
Required tags: team, project, cost_center
Block All-Purpose clusters in prod workspace
Photon mandatory for clusters > 4 workers
PILLAR 3: STORAGE (often overlooked)
VACUUM on schedule β†’ remove old file versions
OPTIMIZE β†’ compact small files (fewer I/O operations)
Predictive Optimization β†’ automates both (UC managed tables)
Archive Bronze after 90 days β†’ move to Cool/Archive tier
Compression: Zstd (default since Runtime 16.0)
PILLAR 4: QUERY EFFICIENCY
Photon β†’ 2-5x faster queries = less DBU consumption
Liquid Clustering β†’ skip irrelevant files = less data scanned
Caching β†’ hot Gold tables stay in memory
Column pruning β†’ SELECT only needed columns, not SELECT *
PILLAR 5: MONITORING & CHARGEBACK
Tag EVERYTHING (clusters, jobs, warehouses)
system.billing.usage table β†’ per-job DBU consumption
Budget alerts β†’ "Alert when team X exceeds $5K/month"
Weekly cost review β†’ spot unused clusters, oversized VMs
Chargeback model β†’ each team/project sees their own spend

Specific example: "At my previous role, we reduced Databricks spend by 45% in 3 months. The three biggest wins were: (1) migrating 50 production jobs from All-Purpose to Job Clusters β€” saved 35%, (2) adding Spot VMs for batch workers β€” saved 25% on worker costs, (3) enforcing auto-termination policies β€” eliminated $8K/month in idle cluster costs."

Interview tip: Lead with compute optimization β€” it's the biggest lever. Give a specific dollar or percentage savings figure. Then walk through policies (prevention), storage, query optimization, and monitoring. End with chargeback β€” it shows organizational maturity.

What NOT to say: "Cost management is the ops team's responsibility." At the senior level, you own the cost efficiency of your pipelines. Also don't say "Just use Serverless for everything" β€” Serverless is great for bursty workloads but can be more expensive than provisioned clusters for steady-state 24/7 streaming.

MOCK PROD Q4: "Compare Databricks Workflows vs Apache Airflow. When would you use each?"

Simple Explanation: Both are workflow orchestrators β€” they schedule and manage dependencies between tasks. Workflows is native to Databricks, while Airflow is platform-agnostic. The choice depends on whether your pipeline lives entirely within Databricks or spans multiple systems.

Technical Depth:

DimensionDatabricks WorkflowsApache Airflow
SetupZero β€” built into DatabricksDeploy, maintain, scale Airflow servers
ScopeDatabricks tasks onlyAny system (Databricks, Snowflake, APIs, S3, etc.)
Task typesNotebook, Python, SQL, Lakeflow, dbt, JARThousands of operators for any platform
Repair/RetryRe-run ONLY failed tasks (built-in "Repair Run")Task retry, but no native selective repair
CostFree (included in Databricks)Infra cost for Airflow servers (or MWAA/Cloud Composer)
MonitoringDatabricks UI + email/webhook alertsAirflow UI + custom integrations
Trigger typesCron, table-triggered (event-driven), file arrivalCron, sensors, event-driven (with plugins)
CI/CDDABs (YAML in Git)DAGs are Python files in Git
Learning curveLow (YAML/UI)Medium-high (Python DAGs, Airflow concepts)

When to use Workflows:

  • Pipeline is 100% Databricks (notebooks, SQL, Lakeflow)
  • Team wants simplicity β€” no extra infra to manage
  • Need table-triggered jobs (event-driven pipelines)
  • Want tight integration with Databricks features (Repair Run, task values, cluster management)

When to use Airflow:

  • Pipeline spans multiple systems (Oracle extraction β†’ Databricks processing β†’ Snowflake loading β†’ API calls)
  • Organization already has Airflow expertise and infrastructure
  • Need advanced patterns: dynamic DAG generation, custom operators, complex branching
  • Multi-cloud or hybrid-cloud environments

Hybrid approach (common in enterprises):

  • Airflow as the outer orchestrator (triggers Databricks jobs, monitors cross-system dependencies)
  • Workflows as the inner orchestrator (manages task dependencies within Databricks)
  • Example: Airflow triggers a Databricks Workflow, waits for it to complete, then triggers a Snowflake load

Interview tip: Don't be dogmatic β€” show you understand both and know when to use which. The best answer is: "It depends on the scope. For Databricks-native pipelines, Workflows is simpler and free. For cross-platform orchestration, Airflow gives you reach. In practice, I've seen hybrid approaches where Airflow handles the outer loop and Workflows handles the inner loop."

What NOT to say: "Airflow is better because it's open source" or "Workflows is better because it's built-in." Both are valid tools β€” the senior answer is understanding the trade-offs and choosing based on the situation. Also don't say "We don't need an orchestrator β€” we just use cron jobs" β€” that shows no understanding of dependency management, retries, or monitoring.

Answer First: Secrets (database passwords, API keys, storage account keys) must NEVER be hardcoded in notebooks, YAML files, or Git. Databricks provides secret scopes backed by Azure Key Vault for secure credential management.

Memory Map: "How do you handle secrets and credentials in Databricks?" -> secret scope stores credential material -> workload identity receives least-privilege access -> code references secrets without logging values -> rotation and audit records verify control [DB_04_Production_CICD_MockInterview.md:1136].

MOCK PROD Q5: "How do you handle secrets and credentials in Databricks?"

Simple Explanation: Secrets (database passwords, API keys, storage account keys) must NEVER be hardcoded in notebooks, YAML files, or Git. Databricks provides secret scopes backed by Azure Key Vault for secure credential management.

Technical Depth:

πŸ—‚οΈARCHITECTURE:
Azure Key Vault (source of truth for all secrets)
↓ backed by
Databricks Secret Scope (access layer within Databricks)
↓ accessed via
dbutils.secrets.get() (in notebooks/jobs)
SETUP:
1. Create Azure Key Vault β†’ store secrets
az keyvault secret set --vault-name travelco-kv \
--name oracle-password --value "s3cur3P@ss!"
2. Create Databricks Secret Scope backed by Key Vault
databricks secrets create-scope --scope travelco-secrets \
--scope-backend-type AZURE_KEYVAULT \
--resource-id /subscriptions/.../vaults/travelco-kv \
--dns-name https://travelco-kv.vault.azure.net/
3. Access in notebooks (value is NEVER printed or logged)
password = dbutils.secrets.get(scope="travelco-secrets", key="oracle-password")
# password is REDACTED in notebook output β†’ shows [REDACTED]
# Even print(password) shows [REDACTED]
BEST PRACTICES:
One Key Vault per environment (dev-kv, staging-kv, prod-kv)
Different secrets per environment (dev DB vs prod DB)
Rotate secrets regularly (Azure Key Vault supports auto-rotation)
Least privilege: only the Service Principal running prod jobs
β”‚ can access prod secrets
Audit: Key Vault logs who accessed which secret and when
NEVER use personal credentials in production jobs
β”‚ β†’ Always use Service Principals with managed identities
Secret scopes in DABs: reference scope names in YAML,
not actual secret values
ANTI-PATTERNS (what NOT to do):
Hardcode passwords in notebooks: password = "myP@ss123"
Store secrets in Git (even in .env files)
Share personal credentials with Service Principals
Use the same secrets across dev/staging/prod
Skip Key Vault and use Databricks-backed scopes in prod
(Key Vault gives you rotation, audit, and RBAC)

Interview tip: Mention the three-layer architecture: Key Vault (storage) β†’ Secret Scope (access layer) β†’ dbutils.secrets.get (code). Emphasize that secrets are automatically redacted in notebook output β€” even print() shows [REDACTED]. This is a security feature, not a bug.

What NOT to say: "We store credentials in environment variables on the cluster" or "We keep a config file with passwords." Both are insecure. Also never say "We hardcode the connection string in the notebook" β€” that's a security violation in any enterprise environment.

Answer First: A sudden performance degradation without code changes points to data, infrastructure, or configuration changes. The investigation follows a priority order from most likely to least likely causes.

Memory Map: "A job that usually takes 30 minutes now takes 4 hours. What do you check?" -> baseline comparison separates queue, scan, shuffle, and write time -> data and plan changes isolate dominant growth -> constrained fix targets that phase -> repeated run proves duration recovery [DB_04_Production_CICD_MockInterview.md:1194].

MOCK PROD Q6: "A job that usually takes 30 minutes now takes 4 hours. What do you check?"

Simple Explanation: A sudden performance degradation without code changes points to data, infrastructure, or configuration changes. The investigation follows a priority order from most likely to least likely causes.

Technical Depth:

πŸ—‚οΈPRIORITY 1: DATA CHANGES (most common cause β€” 70% of cases)
Volume spike:
β”‚ SELECT COUNT(*), SUM(size_in_bytes) FROM source_table
β”‚ WHERE ingestion_date = current_date()
β”‚ β†’ Compare to last 7 days. Did volume 5x overnight?
β”‚ β†’ Cause: holiday rush, backfill, partner data dump
Data skew:
β”‚ Spark UI β†’ Stages β†’ slowest stage β†’ Tasks tab
β”‚ β†’ Is max task duration 100x the median? β†’ SKEW
β”‚ β†’ Which key is skewed?
β”‚ SELECT join_key, COUNT(*) FROM table GROUP BY 1 ORDER BY 2 DESC LIMIT 10
β”‚ β†’ Fix: salting, broadcast join, AQE skew join
Small file accumulation:
β”‚ DESCRIBE DETAIL my_table
β”‚ β†’ numFiles > 10,000? β†’ OPTIMIZE my_table
β”‚ β†’ Was auto-compaction disabled or OPTIMIZE not running?
Schema change:
β†’ Source added 50 new columns β†’ wider rows β†’ more I/O
β†’ Auto Loader schema evolution processing overhead
PRIORITY 2: INFRASTRUCTURE CHANGES (20% of cases)
Cluster config changed:
β”‚ β†’ Fewer workers? Different VM type? Autoscale misconfigured?
β”‚ β†’ Compare current config to last successful run
Spot VM evictions:
β”‚ β†’ High eviction rate β†’ tasks keep retrying on fewer workers
β”‚ β†’ Check Spark UI executors tab for removed executors
Storage throttling:
β”‚ β†’ ADLS Gen2 has rate limits (20K ops/sec per account)
β”‚ β†’ Azure Monitor β†’ Storage β†’ check for 429 throttle errors
Network:
β†’ VNet peering issues, DNS resolution delays
PRIORITY 3: PLATFORM/CONFIGURATION (10% of cases)
Runtime version changed:
β”‚ β†’ Did someone update the Databricks Runtime version?
β”‚ β†’ AQE settings might differ between versions
Spark config changed:
β”‚ β†’ spark.sql.shuffle.partitions, broadcast threshold
β”‚ β†’ Compare spark configs between successful and failed runs
Photon disabled:
β”‚ β†’ Was Photon accidentally turned off?
Cache invalidated:
β†’ Delta cache or disk cache was cleared
IMMEDIATE FIXES (after diagnosis):
Volume spike β†’ increase workers temporarily, optimize partitioning
Data skew β†’ enable AQE, add salting, repartition
Small files β†’ OPTIMIZE + enable auto-compaction
Stale stats β†’ ANALYZE TABLE ... COMPUTE STATISTICS
Cluster change β†’ revert to previous config
Long-term β†’ add duration monitoring alerts

Interview tip: Start with "I'd investigate in priority order: data changes first (70% of cases), then infrastructure (20%), then configuration (10%)." This framing shows you've seen enough production issues to know the probability distribution. Mention specific commands at each step β€” DESCRIBE DETAIL, ANALYZE TABLE, Spark UI tabs.

What NOT to say: "I'd just scale up the cluster." That might mask the problem temporarily but doesn't fix the root cause. Also don't say "I'd check the code for bugs" β€” the question explicitly says there were no code changes, so the cause is external.

Answer First: Testing notebooks requires extracting testable logic from the notebook format and applying a multi-layer testing strategy. The key insight is: notebooks are great for exploration, but production logic should be in testable Python modules.

Memory Map: "How do you test notebooks before deploying to production?" -> notebook logic moves into importable functions -> unit tests run outside interactive state -> integration job uses isolated test data -> CI result gates production deployment [DB_04_Production_CICD_MockInterview.md:1265].

MOCK PROD Q7: "How do you test notebooks before deploying to production?"

Simple Explanation: Testing notebooks requires extracting testable logic from the notebook format and applying a multi-layer testing strategy. The key insight is: notebooks are great for exploration, but production logic should be in testable Python modules.

Technical Depth:

πŸ—‚οΈLAYER 1: REFACTOR β€” Extract logic from notebooks into modules
BAD: All logic inline in notebook cells
β”‚ df = spark.read.table("bookings")
β”‚ df = df.filter(col("status") == "confirmed") # business logic in notebook
β”‚ df = df.withColumn("revenue", col("fare") * col("pax_count"))
GOOD: Logic in Python modules, notebooks just orchestrate
β”‚ # src/transforms/bookings.py
β”‚ def filter_confirmed(df):
β”‚ return df.filter(col("status") == "confirmed")
β”‚ def add_revenue(df):
β”‚ return df.withColumn("revenue", col("fare") * col("pax_count"))
β”‚ # notebooks/silver_bookings.py (thin orchestration layer)
β”‚ from transforms.bookings import filter_confirmed, add_revenue
β”‚ df = spark.read.table("bookings")
β”‚ df = filter_confirmed(df)
β”‚ df = add_revenue(df)
β”‚ df.write.saveAsTable("silver_bookings")
Now filter_confirmed() and add_revenue() are independently testable!
LAYER 2: UNIT TESTS (no Spark needed β€” fast, cheap)
pytest + pandas or PySpark local mode
β”‚ def test_filter_confirmed():
β”‚ data = [("B001", "confirmed", 500), ("B002", "cancelled", 300)]
β”‚ df = spark.createDataFrame(data, ["id", "status", "fare"])
β”‚ result = filter_confirmed(df)
β”‚ assert result.count() == 1
β”‚ assert result.first()["id"] == "B001"
Run in CI pipeline (Azure DevOps build agent)
β”‚ β†’ No Databricks cluster needed = fast + free
New (Feb 2026): pytest integrated in Databricks workspace
LAYER 3: INTEGRATION TESTS (staging cluster)
Deploy to staging with DABs
Run full pipeline on sample data (subset of prod)
Assert:
Output schema matches expected
Row count within expected range
No null values in required columns
Checksums match for deterministic transforms
Data quality expectations pass
Tool: Nutter framework or custom assertions
LAYER 4: DATA QUALITY (built into pipeline)
Lakeflow expectations: @dlt.expect_or_fail ("valid_fare", "fare > 0")
Quality monitoring: auto-detect anomalies (new Feb 2026)
Assertion notebooks: run after pipeline, compare Gold to expected
LAYER 5: PERFORMANCE REGRESSION (before major releases)
Run on staging with prod-scale data sample
Compare duration to baseline (last 10 successful runs)
Alert if > 20% slower β†’ investigate before promoting to prod
Track DBU consumption per run

Interview tip: Start with refactoring β€” "The first step isn't testing, it's making notebooks testable by extracting logic into Python modules." This shows maturity. Then walk through the test pyramid from unit (fast, cheap) to performance (slow, expensive). Mention that unit tests need NO Spark cluster β€” this saves significant cost and time.

What NOT to say: "We test by running the notebook and checking the output visually." That's manual testing β€” not scalable or reliable. Also don't say "Notebooks can't be tested" β€” they can, once you extract logic into modules. And never say "We test in production" β€” that's what staging is for.

Answer First: Databricks offers three types of compute, each optimized for a different workload pattern. Choosing the right type is one of the biggest cost optimization levers.

Memory Map: "Explain the difference between all-purpose, job, and SQL warehouse clusters." -> collaborative workloads use persistent interactive capacity -> production jobs use isolated ephemeral capacity -> BI queries use managed SQL endpoints -> concurrency startup and utilization choose among them [DB_04_Production_CICD_MockInterview.md:1337].

MOCK PROD Q8: "Explain the difference between all-purpose, job, and SQL warehouse clusters."

Simple Explanation: Databricks offers three types of compute, each optimized for a different workload pattern. Choosing the right type is one of the biggest cost optimization levers.

Technical Depth:

πŸ—‚οΈALL-PURPOSE (INTERACTIVE) CLUSTERS
What: Shared clusters for development, exploration, ad-hoc analysis
Lifecycle: Start manually β†’ stays running β†’ terminate manually (or auto-timeout)
Cost: Higher DBU rate (premium for interactive features)
β”‚ β†’ Example: 3.5 DBU/hour for a Standard_DS3_v2
Features: Collaborative notebooks, REPL, debugging, iterative development
Who uses them: Data engineers during development, data scientists
Auto-termination: Can be set (e.g., 60 min idle) β€” MUST enforce via policy
When to use: Development ONLY. Never for production jobs.
JOB CLUSTERS
What: Ephemeral clusters created for a specific job, destroyed after
Lifecycle: Created at job start β†’ runs job β†’ auto-terminated when job finishes
Cost: Lower DBU rate (30-50% cheaper than All-Purpose!)
β”‚ β†’ Example: 2.0 DBU/hour for same Standard_DS3_v2
Features: Optimized for batch execution, no interactive features
Who uses them: Automated production pipelines
Startup time: 3-7 minutes (use Instance Pools to reduce to <2 min)
When to use: ALL production batch jobs. This is the #1 cost savings.
SQL WAREHOUSES
What: Optimized for SQL queries and BI tool connectivity
Types:
Serverless: Instant startup (<10 sec), auto-scale, auto-suspend
β”‚ β”‚ β†’ Best for BI dashboards with variable query load
Pro: Photon-powered, predictable pricing, good for heavy SQL
Classic: Legacy, cheaper but slower startup
Cost: DBU per query (Serverless) or per hour (Pro/Classic)
Features: JDBC/ODBC endpoints, Power BI connector, query caching
Who uses them: BI analysts, Power BI, Tableau, ad-hoc SQL users
When to use: SQL workloads, BI dashboards, data exploration via SQL
DECISION MATRIX:
"I'm developing a notebook" β†’ All-Purpose (interactive features)
"I'm running a production ETL pipeline" β†’ Job Cluster (cost-efficient)
"I'm serving BI dashboards" β†’ Serverless SQL Warehouse (auto-scale)
"I'm running a streaming job 24/7" β†’ Job Cluster + Spot VMs (long-running)
"I'm training an ML model" β†’ Job Cluster with GPU VMs (specialized)
"I'm doing ad-hoc SQL exploration" β†’ SQL Warehouse (SQL-optimized)
COMMON MISTAKE:
Running production jobs on All-Purpose clusters.
β†’ All-Purpose: 3.5 DBU/hr Γ— 24 hrs Γ— 30 days = 2,520 DBU/month
β†’ Job Cluster: 2.0 DBU/hr Γ— 4 hrs Γ— 30 days = 240 DBU/month
β†’ Savings: 90%! (because Job Clusters are cheaper AND don't run when idle)

Interview tip: Lead with the cost implication: "The single biggest cost mistake I see is running production jobs on All-Purpose clusters. Switching to Job Clusters saves 30-50% on the DBU rate alone, plus they auto-terminate β€” so you're not paying for idle time." Then walk through the three types and when to use each.

What NOT to say: "I use All-Purpose clusters for everything because they're convenient." That's the most expensive option and shows no cost awareness. Also don't confuse SQL Warehouses with regular clusters β€” SQL Warehouses are specifically optimized for SQL workloads and BI tool connectivity.

Answer First: Schema changes (new columns, renamed columns, type changes) are one of the most common causes of production pipeline failures. The strategy is: detect early, handle gracefully, alert always.

Memory Map: "How do you handle a schema change in a production pipeline?" -> contract change classifies additive or breaking impact -> controlled evolution updates table metadata -> downstream compatibility tests gate release -> rollback and lineage protect consumers [DB_04_Production_CICD_MockInterview.md:1398].

MOCK PROD Q9: "How do you handle a schema change in a production pipeline?"

Simple Explanation: Schema changes (new columns, renamed columns, type changes) are one of the most common causes of production pipeline failures. The strategy is: detect early, handle gracefully, alert always.

Technical Depth:

πŸ—‚οΈSCENARIO TYPES AND HANDLING:
1. NEW COLUMNS ADDED (most common β€” low risk)
Auto Loader: Set cloudFiles.schemaEvolutionMode = "addNewColumns"
β”‚ β†’ New columns automatically added to target table
β”‚ β†’ Pipeline continues without failure
Delta: .option("mergeSchema", "true") on write
β”‚ β†’ New columns added to Delta table schema
Lakeflow: @dlt.expect handles gracefully with schema evolution
Alert: Notify team that schema changed (even if handled automatically)
2. COLUMNS RENAMED (medium risk)
Auto Loader: Rescue mode captures unrecognized columns in _rescued_data
β”‚ β†’ Pipeline doesn't fail, but renamed columns go to rescue column
β”‚ β†’ Alert triggers investigation
Fix: Add column mapping in Silver layer
β”‚ .withColumnRenamed("old_name", "new_name")
Long-term: Add a schema registry or contract between source and pipeline
Prevent: Upstream schema contracts with source system owners
3. DATA TYPE CHANGED (high risk)
Example: source changes "fare" from INT to STRING
Auto Loader: May fail or miscast data
Fix: Cast explicitly in Silver layer
β”‚ .withColumn("fare", col("fare").cast("decimal(10,2)"))
Delta Type Widening (new 2025): Allows safe type promotions
β”‚ INT β†’ BIGINT, FLOAT β†’ DOUBLE (automatic, no rewrite)
Alert: Type changes always need human review
4. COLUMNS REMOVED (high risk)
Pipeline fails if it references the removed column
Fix: Add null-safe column references
β”‚ col("deleted_column") β†’ coalesce(col("deleted_column"), lit(null))
Bronze: Never fail on missing columns β€” capture everything
Silver: Handle missing columns with defaults or null
Gold: May need business logic changes
PRODUCTION STRATEGY:
Bronze: ALWAYS accept everything (rescue mode, append-only, no schema enforcement)
Silver: Enforce expected schema, handle evolution explicitly
β”‚ β†’ Map, cast, rename in transformation logic
β”‚ β†’ Reject unexpected data with quality expectations
Gold: Strict schema β€” no automatic evolution
β”‚ β†’ Schema changes require a code change + PR + review
Monitoring:
β”‚ β†’ Alert on any schema drift detected by Auto Loader
β”‚ β†’ Alert on _rescued_data column being non-null
β”‚ β†’ Weekly schema drift report comparing source to target
Rollback:
β†’ Delta Time Travel to restore previous schema version
β†’ ALTER TABLE ... DROP COLUMN for accidental additions

Interview tip: Structure your answer by layer: "Bronze is permissive β€” accept everything. Silver is where I enforce and transform. Gold is strict β€” changes require a code change." Mention Auto Loader's rescue mode as your safety net and Delta's schema evolution as your explicit control. End with monitoring β€” "I always alert on schema drift, even when it's handled automatically."

What NOT to say: "I'd just set mergeSchema=true everywhere." That's the lazy approach β€” you'll end up with tables that have hundreds of columns from every source schema change. Also don't say "We stop the pipeline until the source team fixes it" β€” that creates unnecessary downtime. Handle it gracefully at Bronze, fix at Silver.

Answer First: Retry strategies for multi-task workflows need to handle three things: transient failures (retry the task), persistent failures (alert and stop), and dependency management (don't re-run successful upstream tasks). Databricks Workflows has built-in support for all three.

Memory Map: "Design a retry strategy for a multi-task workflow with dependencies." -> transient failures receive bounded exponential retry -> non-idempotent tasks require safe commit boundaries -> dependency graph blocks invalid downstream work -> run repair resumes from verified outputs [DB_04_Production_CICD_MockInterview.md:1465].

MOCK PROD Q10: "Design a retry strategy for a multi-task workflow with dependencies."

Simple Explanation: Retry strategies for multi-task workflows need to handle three things: transient failures (retry the task), persistent failures (alert and stop), and dependency management (don't re-run successful upstream tasks). Databricks Workflows has built-in support for all three.

Technical Depth:

πŸ—‚οΈEXAMPLE WORKFLOW (5-task DAG):
bronze_ingest β†’ silver_transform β†’ gold_aggregate
β†˜ gold_report
quality_check (runs parallel with silver)
RETRY CONFIGURATION PER TASK:
bronze_ingest:
max_retries: 3 (transient failures: network, spot eviction)
min_retry_interval: 60 seconds (backoff between retries)
retry_on_timeout: true
timeout_seconds: 3600 (1 hour max)
Rationale: Ingestion is idempotent (Auto Loader tracks progress)
β†’ Safe to retry without data duplication
silver_transform:
max_retries: 2
min_retry_interval: 120 seconds
retry_on_timeout: true
timeout_seconds: 7200 (2 hours max)
Rationale: MERGE is idempotent (matched rows update, unmatched insert)
β†’ Safe to retry; Delta transactions ensure atomicity
gold_aggregate:
max_retries: 1
timeout_seconds: 3600
Rationale: Aggregation overwrites target (OVERWRITE mode)
β†’ Safe to retry; just recomputes
gold_report:
max_retries: 2
timeout_seconds: 1800
Rationale: Report generation may call external APIs
β†’ Retry for transient API failures
quality_check:
max_retries: 0 (NO retries β€” quality failures are not transient)
timeout_seconds: 600
Rationale: If quality check fails, something is wrong with the data
β†’ Don't retry, ALERT immediately
DATABRICKS YAML CONFIGURATION:
resources:
jobs:
daily_etl:
tasks:
- task_key: bronze_ingest
max_retries: 3
min_retry_interval_millis: 60000
timeout_seconds: 3600
retry_on_timeout: true
notebook_task:
notebook_path: ./notebooks/bronze_ingest.py
- task_key: silver_transform
depends_on:
- task_key: bronze_ingest
max_retries: 2
timeout_seconds: 7200
- task_key: quality_check
depends_on:
- task_key: bronze_ingest
max_retries: 0 # Quality failures should NOT be retried
timeout_seconds: 600
- task_key: gold_aggregate
depends_on:
- task_key: silver_transform
- task_key: quality_check # Only run if quality passed!
REPAIR RUN (handling failures after the fact):
If gold_aggregate fails after bronze + silver succeeded:
β”‚ β†’ "Repair Run" re-runs ONLY gold_aggregate (not bronze or silver)
β”‚ β†’ Saves time and cost β€” no redundant re-processing
If bronze_ingest fails after 3 retries:
β”‚ β†’ Entire downstream DAG is skipped (silver, gold not attempted)
β”‚ β†’ Alert fires β†’ engineer investigates
ADVANCED PATTERNS:
Conditional tasks: Run notification task only on failure
β”‚ depends_on:
β”‚ - task_key: gold_aggregate
β”‚ outcome: FAILED # Only run if upstream failed
Exponential backoff: Increase retry intervals
β”‚ 1st retry: 60 sec, 2nd: 120 sec, 3rd: 240 sec
β”‚ β†’ Gives transient issues time to resolve
Circuit breaker: If job fails 3 consecutive nights,
disable the job and escalate to on-call
β†’ Prevents wasting compute on a persistent failure

Key design principles:

  1. Idempotency first: Every task must produce the same result whether run once or three times
  2. Differentiate transient vs persistent: Retry transient failures (network, spot eviction), alert on persistent failures (data quality, schema)
  3. Don't retry quality checks: If data quality fails, retrying won't fix bad data β€” alert a human
  4. Use Repair Run: Don't re-run the entire DAG when only one task failed
  5. Set timeouts: Every task needs a timeout to prevent runaway costs

Interview tip: Show that your retry strategy is differentiated per task type. "Ingestion tasks get 3 retries because they're idempotent and often fail transiently. Quality checks get 0 retries because failing quality means the data is bad β€” retrying won't help." This shows you've thought about failure modes, not just applied the same config everywhere.

What NOT to say: "Set max_retries=5 on every task." That's a blanket approach that wastes compute on persistent failures and delays alerting. Also don't say "We don't use retries β€” if it fails, we investigate manually." That means every transient failure (network blip, spot eviction) requires human intervention β€” not scalable.

FINAL PREP CHECKLIST

Answer First: The Day 1 production review verifies transaction-log mechanics, ACID conflicts, checkpoint recovery, and safe maintenance before deployment topics.

Memory Map: Day 1 Review (Delta Lake) -> Delta review links log actions to snapshots -> MERGE and schema rules govern mutation -> time travel supports investigation -> maintenance evidence confirms table health [DB_04_Production_CICD_MockInterview.md:1582].

Day 1 Review (Delta Lake)

  • Delta transaction log, ACID, checkpoints
  • MERGE (syntax, duplicates, 6 optimization techniques, schema evolution)
  • OPTIMIZE / VACUUM / Z-ORDER / Liquid Clustering β€” what each does and when to use
  • Time travel recovery (VERSION AS OF, RESTORE, selective MERGE)
  • Lakehouse architecture (vs Lake vs Warehouse)
  • Delta 4.x features (Variant, Type Widening), Predictive Optimization

Answer First: The Day 2 production review follows data from Bronze ingestion through Silver quality controls to Gold contracts and incremental delivery.

Memory Map: Day 2 Review (ETL Pipelines) -> ETL review follows ingestion through medallion layers -> CDC and idempotency protect repeated processing -> expectations quarantine invalid records -> reconciliation confirms published data [DB_04_Production_CICD_MockInterview.md:1590].

Day 2 Review (ETL Pipelines)

  • Medallion Architecture with Examples (Bronze/Silver/Gold decisions)
  • SCD Type 2 β€” merge_key trick in SQL + apply_changes in Lakeflow
  • CDC pipeline design (Oracle β†’ Debezium β†’ Kafka β†’ Bronze β†’ Silver β†’ Gold)
  • CDF (Change Data Feed) vs CDC β€” what's the difference?
  • Auto Loader (modes, schema evolution, rescue mode, vs COPY INTO)
  • Lakeflow Declarative Pipelines (expectations 3 levels, MV vs ST)

Answer First: The Day 3 production review tests Unity Catalog hierarchy, privilege boundaries, identities, storage access, lineage, and GDPR controls.

Memory Map: Day 3 Review (Platform & Governance) -> review connects hierarchy grants identities and storage trust -> policy examples exercise row and column controls -> lineage traces governed dependencies -> audit questions verify operational ownership [DB_04_Production_CICD_MockInterview.md:1598].

Day 3 Review (Platform & Governance)

  • Unity Catalog (hierarchy, 6 pillars, 3-level namespace)
  • Row-level security + Column masking (write the SQL)
  • ABAC (tag-based policies β€” write the SQL)
  • Photon (when to use / NOT use)
  • Serverless vs Job Cluster vs All-Purpose
  • Azure: 3-plane architecture, Key Vault, Service Principals
  • GDPR: deletion pipeline + pseudonymization
  • Delta Sharing for airline partners

Answer First: The Day 4 production review compares orchestration ownership, deployment bundles, monitoring, rollback, and cost evidence for a reliable release.

Memory Map: Day 4 Review (Production & CI/CD) -> review follows source control through tested promotion -> orchestration covers dependencies retries and alerts -> monitoring ties runs to service objectives -> rollback evidence completes production readiness [DB_04_Production_CICD_MockInterview.md:1608].

Day 4 Review (Production & CI/CD)

  • Workflows vs Airflow β€” when to use each
  • Declarative Automation Bundles + Azure DevOps CI/CD
  • Cost management (5 areas)
  • Spark execution plans β€” read bottom up
  • Spark UI debugging methodology
  • System design for a travel platform (workspaces, catalogs, compute, governance)
  • All 10 mock questions practiced OUT LOUD
  • All 10 Production mock questions practiced OUT LOUD

Answer First: Frame every answer with Real-world context β€” "In a travel booking system with 200+ airline partners...".

Memory Map: INTERVIEW DAY TIPS -> opening response states the decision first -> concise mechanism supplies causal depth -> trade-off shows senior judgment -> production evidence closes the answer [DB_04_Production_CICD_MockInterview.md:1620].

INTERVIEW DAY TIPS

  1. Frame every answer with Real-world context β€” "In a travel booking system with 200+ airline partners..."
  2. Show trade-offs, not just answers β€” "Z-ORDER is good but Liquid Clustering is better for new tables because it's incremental and automatic..."
  3. Mention scale β€” "At enterprise scale of billions of daily transactions..."
  4. Know the new names β€” DLT β†’ Lakeflow Declarative Pipelines, Asset Bundles β†’ Declarative Automation Bundles, Databricks Assistant β†’ Genie Code
  5. Be honest about what you don't know β€” "I haven't used Lakebase in production yet, but I understand it's a serverless Postgres for low-latency serving use cases..."
  6. Ask clarifying questions β€” shows you think before jumping to solutions
  7. Think out loud β€” senior interviews value your thought process, not just the answer
  8. Use the STAR method for experience questions β€” Situation, Task, Action, Result

Production & Cost β€” Quick Recall

πŸ—ΊοΈ Memory Map
How to use this file:
  • ⚑ = Must remember (95% chance of being asked)
  • πŸ”‘ = Key concept (core understanding needed)
  • ⚠️ = Common trap (interviewers love to test this)
  • 🧠 = Memory Map (mnemonic/acronym β€” memorize this!)
  • πŸ“ = One-liner (flash-card style β€” cover answer, test yourself)
Reading strategy: Read Memory Maps FIRST β†’ then Direct Questions β†’ then Mid-Level.

🧠 MASTER MEMORY MAP β€” Day 4

🧠 PRODUCTION DATABRICKS = "WCCDS"
PRODUCTION DATABRICKS"WCCDS"
WWorkflows (job scheduling + orchestration)
CCI/CD (Declarative Automation Bundles + Azure DevOps)
CCost Management (5 areas to optimize)
DDebugging (Spark UI + execution plans)
SSystem Design (end-to-end platform architecture)
COST MANAGEMENT"CCSQM" (think: Cost Control Saves Quite Much)
CCompute (right cluster type, auto-terminate)
CCluster Policies (enforce limits on team spending)
SStorage (OPTIMIZE + VACUUM + lifecycle rules)
QQuery Optimization (Photon, caching, predicate pushdown)
MMonitoring + Chargeback (tag everything, show cost per team)
CI/CD = "DAB→Git → Azure DevOps → Deploy"
DABDeclarative Automation Bundles (YAML config files in Git)
Was called: Asset Bundles→renamed 2025

SECTION 1: DATABRICKS WORKFLOWS

🧠 Memory Map: Workflows

🧠 WORKFLOW = "Scheduled pipeline = series of tasks with dependencies"
WORKFLOW"Scheduled pipeline β†’ series of tasks with dependencies"
Example daily pipeline:
Task 1: Bronze ingestion (6:00 AM)
↓ (depends on Task 1)
Task 2: Silver transformation (after Task 1)
↓ (depends on Task 2)
Task 3: Gold aggregation (after Task 2)
↓ (depends on Task 3)
Task 4: Data quality check (after Task 3)
TASK TYPES: Notebook, Python script, SQL, Lakeflow pipeline, dbt, JAR
KEY FEATURES
Repair Run→re-run ONLY failed tasks (don't restart everything!)
Task Values→pass data between tasks (dbutils.jobs.taskValues)
Table-Triggered→start job when Delta table has new data
Backfill→reprocess historical data (GA 2025)
WORKFLOWS vs AIRFLOW:
Workflows = Databricks only, free, simple
Airflow = Multi-platform, requires infra, more flexible
ADFAzure native, good for cross-platform orchestration

⚑ MUST KNOW DIRECT QUESTIONS

Q1What is Databricks Workflows?

Built-in job scheduler in Databricks. Define tasks, set dependencies, schedule runs. Free (included in Databricks).

Q2What task types are supported?
πŸ“ Note
book, Python script, SQL query, Lakeflow pipeline, dbt task, JAR, Spark submit.
Q3What is a Repair Run?

Re-runs ONLY failed tasks in a workflow β€” doesn't restart the whole pipeline. Saves time and compute.

Q4How do tasks pass data to each other?

Using dbutils.jobs.taskValues:

python β€” editable
# Task 1: Save a value
dbutils.jobs.taskValues.set(key="row_count", value=1500000)
# Task 2: Read the value
count = dbutils.jobs.taskValues.get(taskKey="task1", key="row_count")

Q5What is a table-triggered job?

A job that starts automatically when new data arrives in a Delta table (not on a schedule). Uses CDF (Change Data Feed) to detect new rows.

⚠️ Q6Workflows vs Airflow β€” when to use which?
βœ… Pro Tip
  • Workflows: Pipeline is 100% inside Databricks
  • Airflow: Orchestrate across multiple platforms (Databricks + Oracle + APIs)
  • Azure Data Factory: Azure-centric alternative to Airflow
  • ⚠️ Don't say "Airflow is better" β€” say "it depends on the orchestration scope"

SECTION 2: CI/CD

🧠 Memory Map: CI/CD Pipeline

πŸ—‚οΈCI/CD = "Code in Git β†’ Test β†’ Deploy to Databricks automatically"
CI/CD = "Code in Git β†’ Test β†’ Deploy to Databricks automatically"
DECLARATIVE AUTOMATION BUNDLES (DAB):
Old name: "Asset Bundles" β†’ renamed 2025
= YAML configuration files that define your Databricks resources
databricks.yml = "Recipe book for your entire project"
workspace settings
job definitions (workflows)
cluster configs
pipeline definitions
permissions
DEPLOYMENT FLOW:
Developer writes code + databricks.yml
↓
Git push β†’ triggers Azure DevOps pipeline
↓
Azure DevOps runs:
1. databricks bundle validate (check YAML syntax)
2. databricks bundle deploy -t staging (deploy to staging)
3. Run tests on staging
4. databricks bundle deploy -t production (deploy to prod)
ENVIRONMENTS:
databricks.yml defines targets:
dev β†’ developer workspace
staging β†’ QA/testing workspace
prod β†’ production workspace
Remember: "DAB-VDP" = Declarative Automation Bundles β†’ Validate β†’ Deploy β†’ Production

⚑ MUST KNOW DIRECT QUESTIONS

Q7What are Declarative Automation Bundles?

YAML configuration files (in Git) that define ALL Databricks resources β€” jobs, clusters, pipelines, permissions. Deploy with databricks bundle deploy. Formerly called "Asset Bundles."

Q8What is the main config file?

databricks.yml β€” defines workspace, targets (dev/staging/prod), jobs, clusters, and pipeline configurations.

Q9What CLI commands are used?

bash
databricks bundle validate     # Check YAML is correct
databricks bundle deploy -t staging  # Deploy to staging
databricks bundle deploy -t prod     # Deploy to production
databricks bundle destroy -t staging # Remove from staging

Q10How does CI/CD work with Azure DevOps?

  1. Developer pushes code to Git (Azure Repos)
  2. Azure DevOps pipeline triggers automatically
  3. Pipeline runs: validate β†’ deploy to staging β†’ test β†’ deploy to prod
  4. Uses databricks bundle CLI commands in pipeline YAML

πŸ”‘ MID-LEVEL QUESTIONS

Q11Show a basic databricks.yml example
πŸ“ Note
yaml
bundle:
  name: travelco_booking_pipeline    # Project name

targets:
  staging:                           # Staging environment
    workspace:
      host: https://adb-staging.azuredatabricks.net
    default: true                    # Default target
  production:                        # Production environment
    workspace:
      host: https://adb-prod.azuredatabricks.net
    run_as:
      service_principal_name: sp-booking-pipeline  # Not personal account!

resources:
  jobs:
    daily_booking_etl:               # Job definition
      name: "Daily Booking ETL"
      schedule:
        quartz_cron_expression: "0 0 6 * * ?"  # 6 AM daily
      tasks:
        - task_key: bronze_ingest
          notebook_task:
            notebook_path: ./notebooks/bronze_ingest.py
        - task_key: silver_transform
          depends_on:
            - task_key: bronze_ingest     # Runs AFTER bronze
          notebook_task:
            notebook_path: ./notebooks/silver_transform.py
Q12What testing strategies exist for Databricks?

4 levels:

  1. Unit tests β€” Test Python functions (no Spark needed, use pytest)
  2. Integration tests β€” Test with real Spark session (use staging workspace)
  3. Data quality tests β€” Lakeflow Expectations on Silver/Gold tables
  4. End-to-end tests β€” Run full pipeline on sample data, validate output

Q13Azure DevOps pipeline YAML example

yaml
trigger:
  branches:
    include: [main]              # Trigger on push to main

stages:
  - stage: Deploy_Staging
    jobs:
      - job: deploy
        steps:
          - script: pip install databricks-cli
          - script: databricks bundle validate
          - script: databricks bundle deploy -t staging

  - stage: Deploy_Production
    dependsOn: Deploy_Staging     # Only after staging succeeds
    condition: succeeded()
    jobs:
      - job: deploy
        environment: production    # Requires approval gate
        steps:
          - script: databricks bundle deploy -t production

SECTION 3: COST MANAGEMENT

🧠 Memory Map: Cost

🧠 Memory Map
5 COST AREAS = "CCSQM"
1. COMPUTE (biggest cost β€” 60-70% of Databricks bill)
β†’ Use Job Clusters for production (auto-terminate)
β†’ Use Spot VMs (60-90% cheaper, but can be reclaimed)
β†’ Auto-terminate idle clusters (set to 10-15 minutes)
β†’ Right-size: start small, scale up if needed
2. CLUSTER POLICIES (prevent overspending)
β†’ Admins set max nodes, allowed VM types, auto-terminate rules
β†’ Teams can't create 100-node clusters accidentally
3. STORAGE (usually 10-20% of cost)
β†’ OPTIMIZE (compact files β†’ fewer API calls)
β†’ VACUUM (delete unused files β†’ save storage)
β†’ Lifecycle rules on ADLS (move old Bronze to Cool/Archive tier)
4. QUERY OPTIMIZATION (save compute time = save money)
β†’ Photon (faster queries β†’ less compute time)
β†’ Liquid Clustering (skip irrelevant files)
β†’ Caching (spark.databricks.io.cache.enabled = true)
5. MONITORING + CHARGEBACK
β†’ Tag clusters with team/project (cost allocation)
→ Databricks account console→cost by workspace/cluster
β†’ Set budget alerts (notify when 80% budget used)
SPOT VMs:
What: Azure VMs at 60-90% discount
Risk: Azure can reclaim them with 30-sec notice
Use for: Workers (not driver!) in non-critical jobs
⚠️Never use Spot for driver node β€” job dies if reclaimed

⚑ MUST KNOW DIRECT QUESTIONS

Q14What is the biggest cost in Databricks?

Compute (60-70%). Choose right cluster type: Job Cluster for prod, Serverless for SQL, Spot VMs for non-critical batch jobs.

Q15What are Spot VMs?

Azure VMs at 60-90% discount. Azure can reclaim them anytime (30-sec notice). Use for worker nodes only, never for the driver.

Q16What are Cluster Policies?

Admin-defined rules that restrict what clusters teams can create. Example: max 10 nodes, only Standard_D4s_v3 VMs, auto-terminate after 15 min.

Q17How do you implement chargeback?

Tag every cluster/job with team name and project. Use Databricks account console to see cost per tag. Set budget alerts.

⚠️ Q18What is the #1 cost mistake?

Using All-Purpose clusters for production jobs. They stay running 24/7 even when idle. Switch to Job Clusters (auto-terminate) β€” saves 60-80%.

πŸ”‘ MID-LEVEL QUESTIONS

Q19Design a cost governance strategy for 200+ engineers
πŸ—ΊοΈ Memory Map
🧠 Memory Map
1. CLUSTER POLICIES (prevent)
β†’ Dev: max 4 nodes, auto-terminate 15 min
β†’ Prod: max 20 nodes, Spot workers, Job Cluster only
β†’ Data Science: GPU clusters with 2-hour timeout
2. TAGGING (track)
β†’ Mandatory tags: team, project, cost_center
β†’ Automated via cluster policies (users can't skip)
3. MONITORING (alert)
β†’ Weekly cost reports per team
β†’ Budget alerts at 80% threshold
β†’ Anomaly detection (team X cost jumped 300%)
4. OPTIMIZATION (reduce)
β†’ Predictive Optimization (auto OPTIMIZE + VACUUM)
β†’ Serverless SQL Warehouses (no idle cost)
β†’ Reserved capacity for stable workloads (1-year commitment = 30-50% off)

SECTION 4: SPARK DEBUGGING

🧠 Memory Map: Debugging

🧠 DEBUGGING = "Read the Spark UI like a doctor reads an X-ray"
DEBUGGING"Read the Spark UI like a doctor reads an X-ray"
SPARK EXECUTION PLAN
Read BOTTOM to TOP! (physical plan starts at the bottom)
== Physical Plan ==
*(5) HashAggregate ← Step 5: Final aggregation (TOP = last step)
+- *(4) Exchange ← Step 4: Shuffle (data moves between nodes)
+- *(3) HashAggregate ← Step 3: Partial aggregation
+- *(2) Project ← Step 2: Column selection
+- *(1) Scan ← Step 1: Read table (BOTTOM = first step)
RED FLAGS in execution plan:
Exchange→SHUFFLE (expensive! data crosses network)
BroadcastExchange→Small table broadcast (GOOD — no shuffle)
SortMergeJoin→Both tables are large (expensive)
BroadcastHashJoin→One table is small (cheap — GOOD)
CartesianProductβ†’CROSS JOIN (⚠️ usually a mistake!)
SPARK UI β€” 5 TABS TO CHECK:
1. Jobs→Overall progress (how many stages/tasks)
2. Stages→Where is time spent? (longest stage = bottleneck)
3. SQL→Query plan visualization
4. Storage→Cached data
5. Environment→Spark config values
Remember: "JSSEE" = Jobs, Stages, SQL, Storage, Environment
COMMON ISSUES"SSOS"
SSkew (one partition has 100x more data than others)
SSpill (not enough memory β†’ data written to disk β†’ slow)
OOOM (Out of Memory β€” increase executor memory)
SSmall files (too many tiny files β†’ slow reads β†’ OPTIMIZE)

⚑ MUST KNOW DIRECT QUESTIONS

Q20How do you read a Spark execution plan?

Read bottom to top. Bottom = first step (scan table). Top = last step (output). Look for Exchange (shuffle β€” expensive) and BroadcastHashJoin (good β€” no shuffle).

Q21What is a shuffle?

Data movement across network between Spark executors. Happens during GROUP BY, JOIN, DISTINCT, REPARTITION. Expensive β€” minimize shuffles for better performance.

Q22What is data skew?

One partition has much more data than others. Example: 99% of bookings are for "Delhi" airport β†’ one executor does all the work while others sit idle.

Q23How to fix data skew?

  1. Salting: Add random number to skewed key β†’ spreads data across partitions
  2. Broadcast join: If one side is small (<10 MB), broadcast it
  3. Adaptive Query Execution (AQE): Spark auto-detects and fixes skew (enabled by default)

Q24What is spill?

When Spark runs out of executor memory, it writes data to disk (SSD). Much slower than in-memory processing. Fix: increase spark.executor.memory or reduce partition size.

Q25What causes OOM (Out of Memory)?

  1. Too much data in one partition (skew)
  2. Collecting large dataset to driver (df.collect() on millions of rows)
  3. Too many broadcast joins (broadcasting large tables)

πŸ”‘ MID-LEVEL QUESTIONS

⚑ Q26Pipeline is suddenly 5x slower, no code change. What do you check?

Systematic debugging approach (in this order):

  1. Data volume β€” Did input data suddenly 10x? (check Bronze row counts)
  2. Data skew β€” One partition has disproportionate data? (check Spark UI β†’ Stages β†’ task duration variance)
  3. Cluster issues β€” Spot VMs reclaimed? Fewer nodes? (check cluster events log)
  4. Upstream changes β€” Source schema changed? New columns? (check Auto Loader _rescued_data)
  5. Concurrent jobs β€” Other jobs competing for resources? (check workspace activity)
  6. Storage β€” Too many small files? (check table metrics, run OPTIMIZE)
  7. Spark config β€” Someone changed spark configs? (check Environment tab)

Q27How to use the Spark UI to find bottlenecks?
πŸ—ΊοΈ Memory Map
πŸ“‹ Overview
Step 1: Jobs tab→find the slowest job
Step 2: Click it→Stages tab → find the slowest stage
Step 3: Click stage→Tasks tab → look at:
Task duration: all similar? (good) or one task 100x longer? (SKEW)
Shuffle read/write: very large? (shuffle problem)
Spill (Memory/Disk): any spill? (need more memory)
Step 4: SQL tab→execution plan → look for Exchange (shuffle)

SECTION 5: SYSTEM DESIGN

πŸ”‘ MID-LEVEL QUESTIONS

⚑ Q28Design a data platform for a travel platform on Azure Databricks
πŸ—ΊοΈMemory Map
```
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ TRAVEL PLATFORM DATA ARCHITECTURE β”‚
────────────────────────────────────────────────────────────
β”‚ β”‚
β”‚ SOURCES: β”‚
β”‚ Oracle (bookings) β†’ Debezium β†’ Kafka β†’ ADLS landing zone β”‚
β”‚ Flight APIs (JSON) β†’ Event Hubs β†’ ADLS landing zone β”‚
β”‚ Partner files (CSV) β†’ SFTP β†’ ADLS landing zone β”‚
β”‚ β”‚
β”‚ INGESTION: β”‚
β”‚ Auto Loader (cloudFiles) β†’ Bronze Delta tables β”‚
β”‚ Mode: File Notification (scale: billions/day) β”‚
β”‚ β”‚
β”‚ PROCESSING: β”‚
β”‚ Lakeflow Pipelines: β”‚
β”‚ Bronze β†’ Silver (MERGE + dedup + SCD2 + PII tagging) β”‚
β”‚ Silver β†’ Gold (aggregations + star schema) β”‚
β”‚ Quality: Expectations (Warn/Drop/Fail per layer) β”‚
β”‚ β”‚
β”‚ SERVING: β”‚
β”‚ Serverless SQL Warehouse β†’ Power BI dashboards β”‚
β”‚ Delta Sharing β†’ Partner airline data access β”‚
β”‚ Feature Store β†’ ML model training β”‚
β”‚ β”‚
β”‚ GOVERNANCE: β”‚
β”‚ Unity Catalog (3-level namespace) β”‚
β”‚ Row-level security (airline isolation) β”‚
β”‚ Column masking (PII/GDPR) β”‚
β”‚ ABAC tags for automated policy enforcement β”‚
β”‚ β”‚
β”‚ OPERATIONS: β”‚
β”‚ CI/CD: Declarative Automation Bundles + Azure DevOps β”‚
β”‚ Monitoring: Databricks Workflows + budget alerts β”‚
β”‚ Compute: Job Clusters (prod) + Spot workers + auto-term β”‚
β”‚ Cost: Cluster policies + tagging + chargeback β”‚
β”‚ β”‚
β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
```
Q29What makes a good answer to system design questions?

Follow the "RIGS" framework:

  1. Requirements β€” Clarify scale, latency, users, compliance
  2. Ingestion β€” How data enters (Auto Loader, Kafka, APIs)
  3. Governance β€” Security, PII, GDPR, access control
  4. Serving β€” How consumers access (SQL Warehouse, Delta Sharing, APIs)
Always mention: Medallion layers, Unity Catalog, cost strategy, monitoring

🧠 FINAL REVISION β€” Day 4 Summary Card

πŸ“ Architecture Diagram
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚               DAY 4: PRODUCTION & CI/CD                      β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                                             β”‚
β”‚  Workflows = built-in scheduler (free, Databricks-only)     β”‚
β”‚  Repair Run = re-run ONLY failed tasks                      β”‚
β”‚  Task Values = pass data between tasks (dbutils)            β”‚
β”‚  Table-triggered = start when new data arrives              β”‚
β”‚                                                             β”‚
β”‚  CI/CD = Declarative Automation Bundles (was Asset Bundles)  β”‚
β”‚  Config: databricks.yml (YAML in Git)                       β”‚
β”‚  Flow: validate β†’ deploy staging β†’ test β†’ deploy prod       β”‚
β”‚  Azure DevOps pipeline triggers on Git push                 β”‚
β”‚  ⚠️ Always use Service Principal for prod (not personal!)   β”‚
β”‚                                                             β”‚
β”‚  Cost = "CCSQM"                                             β”‚
β”‚  Compute: Job Cluster + Spot VMs + auto-terminate           β”‚
β”‚  Cluster Policies: enforce limits per team                  β”‚
β”‚  Storage: OPTIMIZE + VACUUM + ADLS lifecycle                β”‚
β”‚  Query: Photon + Liquid Clustering + caching                β”‚
β”‚  Monitoring: tagging + budget alerts + chargeback           β”‚
β”‚  ⚠️ #1 mistake: All-Purpose clusters for production!       β”‚
β”‚                                                             β”‚
β”‚  Debugging = "SSOS" (Skew, Spill, OOM, Small files)         β”‚
β”‚  Read execution plan BOTTOM to TOP                          β”‚
β”‚  Exchange = shuffle (expensive!)                            β”‚
β”‚  BroadcastHashJoin = good (no shuffle)                      β”‚
β”‚  Spark UI: Jobs β†’ Stages β†’ Tasks (find bottleneck)          β”‚
β”‚                                                             β”‚
β”‚  System Design: "RIGS" framework                            β”‚
β”‚  Requirements β†’ Ingestion β†’ Governance β†’ Serving            β”‚
β”‚                                                             β”‚
β”‚  TOP 5 THINGS TO SAY IN INTERVIEW:                          β”‚
β”‚  1. "Workflows with Repair Run for failed task recovery"    β”‚
β”‚  2. "DAB + Azure DevOps for automated CI/CD"                β”‚
β”‚  3. "Job Clusters + Spot VMs + auto-terminate for cost"     β”‚
β”‚  4. "Spark UI: check stages for skew + spill"               β”‚
β”‚  5. "Unity Catalog + ABAC + row security for governance"    β”‚
β”‚                                                             β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
πŸ—ΊοΈ Memory Map
Study tip: Read this file TWICE:
  1. First pass (30 min): Read only 🧠 Memory Maps + ⚑ Direct Questions
  2. Second pass (30 min): Read πŸ”‘ Mid-Level Questions + ⚠️ Traps
  3. Before interview (15 min): Read ONLY the Final Revision Summary Card

🧠 INTERVIEW DAY: ULTRA-QUICK CHEAT SHEET (Read 10 min before interview)

πŸ“ Architecture Diagram
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚              ALL 4 DAYS β€” LAST-MINUTE RECALL                     β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                                                 β”‚
β”‚  DELTA = Parquet + _delta_log (ACID on data lake)               β”‚
│  MERGE = Match→Update, NotMatch→Insert (deduplicate source!)    │
β”‚  OPTIMIZE = compact files | VACUUM = delete old files (7 days)  β”‚
│  Liquid Clustering > Z-ORDER > Partitioning (new→old)           │
β”‚                                                                 β”‚
β”‚  MEDALLION = Bronze(raw) β†’ Silver(clean) β†’ Gold(business)       β”‚
β”‚  SCD2 = merge_key trick OR apply_changes(scd_type=2)            β”‚
β”‚  CDC = Oracle β†’ Debezium β†’ Kafka β†’ Auto Loader β†’ Delta          β”‚
β”‚  Auto Loader = cloudFiles (Directory or Notification mode)      β”‚
β”‚  Lakeflow = Declarative pipelines + Expectations (Warn/Drop/Fail)β”‚
β”‚                                                                 β”‚
β”‚  Unity Catalog = catalog.schema.table (3 levels)                β”‚
β”‚  Security: Row filter + Column mask + ABAC tags                 β”‚
β”‚  Photon = C++ SQL engine (not for Python UDFs/ML)               β”‚
β”‚  Compute: All-Purpose(dev) β†’ Job Cluster(prod) β†’ Serverless    β”‚
β”‚  GDPR: DELETE + VACUUM 0 HOURS                                  β”‚
β”‚                                                                 β”‚
β”‚  Workflows = built-in scheduler + Repair Run                    β”‚
β”‚  CI/CD = Declarative Automation Bundles (databricks.yml in Git)  β”‚
β”‚  Cost: Job Clusters + Spot VMs + Cluster Policies + Tagging     β”‚
│  Debug: Spark UI bottom→top, look for Exchange(shuffle)         │
β”‚                                                                 β”‚
β”‚  ALWAYS FRAME WITH TRAVEL CONTEXT:                                     β”‚
β”‚  "In our travel booking pipeline with billions of daily events..."β”‚
β”‚  "For GDPR with 200+ airline partners and passenger PII..."     β”‚
β”‚  "At enterprise scale with Oracle legacy migration to Delta Lake..."β”‚
β”‚                                                                 β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

TOPIC 4: PRODUCTION & CI/CD (Workflows, Asset Bundles, Cost Management, Debugging)

L1 β€” Direct / Simple Questions

  1. What is Databricks Workflows (formerly Jobs)?
    πŸ“ Note
    Databricks' built-in job scheduler for orchestrating tasks (notebooks, SQL, Python, JAR) with dependencies, schedules, retries, and monitoring. Includes features like Repair Run (re-run only failed tasks) and table-triggered jobs.

2. What is the difference between a Task and a Job in Databricks Workflows?

βœ… Pro Tip
A Job is the top-level entity (schedule, alerts, permissions). A Task is a single unit of work within a job (run a notebook, SQL query, or Python script). A job can have multiple tasks with dependencies forming a DAG.

3. What are Databricks Asset Bundles (DABs)?

βœ… Pro Tip
Infrastructure-as-code for Databricks β€” define jobs, clusters, pipelines, and permissions as YAML files in Git. Deploy to multiple environments (dev/staging/prod) with one command. Renamed to Declarative Automation Bundles in March 2026.

4. What is the Databricks CLI?

A command-line tool for interacting with Databricks workspaces. Used for deploying bundles (databricks bundle deploy), managing clusters, running jobs, and managing secrets. Installed via pip install databricks-cli.

5. What is a job cluster vs an all-purpose (interactive) cluster?

Job cluster: created for a specific job, auto-terminated after completion, lower DBU rate (30-50% cheaper). All-purpose: shared interactive cluster for development, stays running until manually stopped or auto-terminated, higher DBU rate. Use job clusters for production, all-purpose for development only.

6. What is cluster autoscaling and how does it work?

Automatically adjusts the number of worker nodes based on workload. Set min_workers and max_workers. Spark adds workers when tasks are queued, removes workers when idle. Saves cost by not over-provisioning. Autoscaling reacts to load, not predicted β€” slight delay in scaling up.

7. What is a cluster policy in Databricks?

A set of rules that constrain cluster creation. Examples: max worker count, allowed VM types, mandatory auto-termination, required tags. Enforced at cluster creation time β€” users cannot override policy constraints. Essential for cost governance and preventing waste.

8. What are spot instances and how do they reduce cost?

Spare cloud VMs offered at up to 90% discount. Can be reclaimed with 30-second notice. Databricks uses them for worker nodes (driver always on-demand). If a spot worker is evicted, Spark retries the task on another worker. Best for batch processing; risky for the driver node.

9. What is the Databricks REST API used for?

Programmatic access to Databricks resources: create/manage clusters, submit jobs, manage secrets, query job status. Used by CI/CD tools (Azure DevOps, GitHub Actions) to automate deployments and by monitoring systems to check job health.

10. What is a Databricks repo (Git integration)? > Direct Git integration in the Databricks workspace. Clone repos, pull/push changes, create branches β€” all from the Databricks UI. Notebooks are synced with Git. Enables version control for notebooks without leaving Databricks. Supports GitHub, Azure DevOps, GitLab.

11. What are Databricks Notebooks vs IDE-based development? > Notebooks: interactive, cell-by-cell execution, great for exploration and prototyping, built-in visualization. IDE (VS Code, PyCharm): better for production code, refactoring, testing, type checking, debugging. Best practice: prototype in notebooks, productionize in IDE with Python modules.

12. What is the difference between a wheel file and a notebook task in a Workflow? > Wheel file: a packaged Python library installed on the cluster. Used for production-quality code with proper module structure, dependencies, and versioning. Notebook task: runs a notebook directly. Wheel files are better for production (testable, versioned, reusable); notebooks for prototyping and orchestration.

13. What is the Ganglia UI / Spark UI used for in debugging? > Ganglia: cluster-level metrics (CPU, memory, disk, network utilization across all nodes). Spark UI: job-level metrics (stages, tasks, shuffle, spill, execution plan). Use Ganglia to check if the cluster is under-utilized or over-loaded. Use Spark UI to diagnose specific job performance issues.

14. What is a driver log vs an executor log? > Driver log: output from the main program (print statements, exceptions, orchestration logic). Located on the driver node. Executor log: output from tasks running on worker nodes (task-level errors, OOM, data processing issues). Check driver logs for the initial error; executor logs for task-specific failures.

15. What is the Databricks DBU (Databricks Unit) and how is pricing calculated? > A unit of processing capability per hour. Cost = DBUs consumed x DBU price (varies by compute type). All-Purpose DBU rate > Job Cluster DBU rate > Serverless rate varies. Total bill = Databricks DBU cost + Azure VM cost. Monitor via system.billing.usage system table.

16. What are init scripts and when would you use them? > Shell scripts that run when a cluster starts, before Spark initializes. Use for: installing OS-level packages, configuring system settings, setting environment variables. Types: cluster-scoped (run on one cluster) or global (run on all clusters). Avoid for Python packages β€” use %pip install instead.

17. What is a multi-task workflow (DAG) in Databricks? > A workflow with multiple tasks connected by dependencies, forming a Directed Acyclic Graph. Example: task A and B run in parallel, task C runs after both complete. Supports conditional logic, retry per task, and Repair Run (re-run only failed tasks without re-running successful ones).

18. What is the dbutils library and what are its key modules? > A Databricks utility library available in notebooks. Key modules: dbutils.fs (file system operations), dbutils.secrets (access secrets), dbutils.widgets (parameterize notebooks), dbutils.jobs.taskValues (pass values between tasks), dbutils.notebook.run() (call other notebooks).

19. What are widgets in Databricks notebooks? > Input parameters for notebooks. Types: text, dropdown, combobox, multiselect. Created with dbutils.widgets.text("env", "dev"), accessed with dbutils.widgets.get("env"). Used to parameterize notebooks for different environments or date ranges without modifying code.

20. What is the difference between %run and dbutils.notebook.run()? > %run: executes another notebook in the SAME context (shares variables, same cluster). dbutils.notebook.run(): executes in a NEW context (isolated, returns a string result). Use %run for shared utility functions. Use dbutils.notebook.run() for orchestration where isolation is needed.

L2

  1. Explain Databricks Asset Bundles (DABs) β€” how do they enable CI/CD for Databricks projects?

    Key points: DABs define jobs, clusters, pipelines, and permissions as YAML in Git. databricks bundle validate checks config syntax. databricks bundle deploy --target prod deploys to any environment. Same YAML, different targets β€” eliminates environment drift. CI/CD pipeline: commit β†’ validate β†’ unit test β†’ deploy staging β†’ integration test β†’ manual approval β†’ deploy prod. DABs replace manual UI-based job creation with reproducible, versioned infrastructure-as-code.

2. Compare DABs vs Terraform for Databricks infrastructure management β€” when to use each?

Key points: DABs: Databricks-native, understands jobs/pipelines/clusters, simpler YAML syntax, handles code + config together. Terraform: cloud-agnostic, manages both Databricks AND Azure infrastructure (VNets, storage accounts, Key Vaults), steeper learning curve. Use DABs for Databricks-specific resources (jobs, pipelines). Use Terraform for infrastructure (workspaces, networking, storage). Many teams use both: Terraform for infra, DABs for pipeline deployment.

3. How do you implement a CI/CD pipeline for Databricks using Azure DevOps?

Key points: (1) Trigger on push to main/develop, (2) Validate stage: install deps, lint (ruff), unit test (pytest), validate bundle, (3) Deploy staging: databricks bundle deploy --target staging, run integration tests, (4) Deploy prod: manual approval gate, databricks bundle deploy --target prod. Service principal authentication. Branch strategy: feature β†’ PR β†’ main β†’ auto-deploy staging β†’ approve β†’ prod. Rollback: redeploy previous Git commit.

4. Explain how Databricks Workflows handles task dependencies, retries, and conditional execution.

Key points: Dependencies: depends_on in YAML creates a DAG. Tasks run when all dependencies succeed. Retries: max_retries, min_retry_interval_millis, retry_on_timeout per task. Conditional: depends_on with outcome: FAILED (run only if upstream failed β€” for notification tasks). Repair Run: re-execute only failed tasks without re-running successful upstream tasks. Task values: pass metadata between tasks via dbutils.jobs.taskValues.

5. Compare job clusters vs all-purpose clusters β€” cost, startup time, use cases.

Key points: Job clusters: 30-50% cheaper DBU rate, 3-7 min startup (or <2 min with pools), auto-terminate after job, best for production. All-purpose: higher DBU rate, stays running (idle cost), collaborative features, best for development. Cost example: 10 daily jobs on All-Purpose ($5K/month) vs Job Clusters ($2K/month). Use pools to reduce Job Cluster startup time. Never run production on All-Purpose.

6. How do you debug an OOM (Out of Memory) error in a Databricks Spark job?

Key points: (1) Check Spark UI executors tab for dead executors (OOM kills), (2) Check task tab for skewed tasks (one task processing disproportionate data), (3) Check driver vs executor OOM: driver OOM = collect() or toPandas() on large data; executor OOM = large partition or skewed join. Fixes: increase spark.executor.memory, repartition to reduce partition size, broadcast small tables, avoid collect(), enable AQE for skew handling. Increase workers, not memory per worker, when possible.

7. Explain how to read and interpret the Spark UI: stages, tasks, shuffle read/write, spill.

Key points: Jobs tab: find slowest job. Stages tab: find bottleneck stage (longest duration, most shuffle). Tasks tab: compare max vs median duration (100x difference = skew). Shuffle Read/Write: data movement between stages β€” minimize by broadcasting small tables. Spill (Memory/Disk): data that didn't fit in memory β€” increase memory or reduce partition size. SQL tab: execution plan, rows at each operator, join strategy.

8. How do you implement blue-green or canary deployments for Databricks ETL pipelines?

Key points: Blue-green: maintain two versions of the pipeline. Deploy new version (green) alongside old (blue). Run green on staging data, validate. Switch production traffic to green. Keep blue as rollback. In Databricks: use DABs with different target names (prod-blue, prod-green). Canary: deploy new version for a subset of data (e.g., one airline partner). Monitor quality and performance. If good, roll out to all. If bad, revert to old version.

9. Explain cluster pool strategy β€” how do pools reduce cluster startup time and cost?

βœ… Pro Tip
Key points: A pool maintains a set of warm, idle VMs ready for cluster creation. When a cluster requests a node, it gets one from the pool (seconds) instead of provisioning a new VM (minutes). Cost: you pay for idle pool VMs (at a lower rate), but clusters start faster. Strategy: shared pool for job clusters (multiple jobs share the pool), separate pools for different VM types. Size the pool based on concurrent job count.

10. How do you manage secrets and environment-specific configurations across dev/staging/prod? > Key points: Secrets: Azure Key Vault per environment (dev-kv, staging-kv, prod-kv). Databricks secret scopes backed by respective Key Vaults. Same scope name (db-secrets) in each environment, different underlying Key Vault. Config: DABs targets section defines per-environment overrides (workspace URL, cluster size, service principal). Code references scope names, not actual values β€” environment-agnostic.

11. Compare Databricks Repos (Git integration) vs external CI/CD tools for version control. > Key points: Databricks Repos: in-workspace Git UI, good for notebook development, limited CI/CD capabilities (no automated testing/deployment). External CI/CD (Azure DevOps, GitHub Actions): full automation, lint/test/deploy pipelines, approval gates, artifact management. Best practice: use Repos for notebook development + DABs/Azure DevOps for CI/CD. Repos is for version control; CI/CD tools are for deployment automation.

12. How do you implement data pipeline monitoring and alerting in Databricks? > Key points: (1) Job-level: Workflows built-in email/webhook alerts on failure, (2) Data quality: Lakeflow expectations with anomaly detection, (3) Custom: write metrics to a monitoring Delta table, build DBSQL dashboard, (4) SLA monitoring: compare actual completion time to target, alert if SLA at risk, (5) Cost: system tables dashboard for spend anomalies. Tools: PagerDuty/OpsGenie for critical alerts, Slack for informational, DBSQL dashboards for visibility.

13. Explain the cost implications of spot instances vs on-demand for different workload types. > Key points: Spot: up to 90% cheaper but can be evicted. Good for: batch ETL (tasks retry on eviction), development (low criticality), training (checkpointed). Bad for: driver node (single point of failure), streaming (eviction causes reprocessing delay), time-critical SLA jobs. Strategy: driver always on-demand. Workers: mix of on-demand (minimum baseline) + spot (autoscale burst). Savings: typically 50-70% on worker costs.

14. How do you diagnose data skew in a Spark job using the Spark UI? > Key points: Stages tab: find the slowest stage. Tasks tab: sort by duration β€” if max task is 100x the median, that's skew. Also check: Shuffle Read Size per task (one task reading much more data = skew on the shuffle key). Fix: (1) salting (add random prefix to skewed key, join on salted key), (2) broadcast join (if one side is small enough), (3) AQE skew join (automatic in DBR 12+), (4) repartition on a more uniform key.

15. What is the recommended project structure for a Databricks DABs project? > Key points: Root: databricks.yml (bundle config), azure-pipelines.yml (CI/CD). Directories: src/notebooks/ (pipeline notebooks), src/lib/ (shared Python modules), tests/unit/ (pytest), tests/integration/ (Nutter), resources/ (additional YAML for complex configs). Key files: requirements.txt, .databrickscfg (auth profiles), .gitignore. Separate notebooks from library code for testability.

16. How do you implement parameterized jobs with dynamic values in Workflows? > Key points: (1) Widgets: dbutils.widgets.get("date") in notebook, set via job parameters, (2) Task values: pass computed values between tasks via dbutils.jobs.taskValues.set/get, (3) Job parameters: defined in DABs YAML, overridden at run time, (4) Dynamic references: {{job.start_time}}, {{task.run_id}} in YAML. Use widgets for simple parameters (date, environment). Use task values for runtime-computed values (record count, status).

17. Explain the difference between task values (dbutils.jobs.taskValues) and widget parameters. > Key points: Widgets: input parameters set before/at job start, static throughout the run, user-facing (UI dropdown). Task values: runtime values set BY tasks, dynamic (computed during execution), used for inter-task communication. Example: widget = run_date parameter. Task value = record_count computed by bronze task and read by silver task. Both are lightweight metadata, not large datasets.

18. How do you handle failing tasks in a DAG β€” retry policies, timeout, conditional logic? > Key points: Retry: max_retries per task (different for each task type β€” idempotent tasks can retry more). Timeout: timeout_seconds prevents runaway tasks. Conditional: depends_on with outcome (FAILED, SUCCEEDED) for notification/cleanup tasks. Repair Run: manual or automated re-run of only failed tasks. Best practice: differentiate retry strategy by failure type β€” transient failures (network, spot) get retries; data quality failures get 0 retries and immediate alerting.

19. Compare Serverless jobs vs provisioned clusters for job execution β€” cost breakeven analysis. > Key points: Serverless: higher per-DBU rate but zero startup cost and zero idle time. Provisioned: lower per-DBU rate but 3-7 min startup time and potential idle time. Breakeven: jobs running <30 min favor Serverless (startup cost dominates provisioned). Jobs running >2 hours favor provisioned (lower rate wins over time). Jobs running multiple times per day favor Serverless (no idle between runs). Calculate: (serverless DBU rate x duration) vs (provisioned DBU rate x duration + startup overhead).

20. How do you implement logging and observability for production Databricks pipelines? > Key points: (1) Structured logging: Python logging module with JSON format (timestamp, job_id, task, level, message), (2) Write logs to Delta table for querying: spark.createDataFrame(log_entries).write.mode("append").saveAsTable("ops.pipeline_logs"), (3) Metrics: record duration, row counts, error counts per pipeline run, (4) Dashboards: DBSQL dashboard with pipeline health overview, (5) Alerts: webhook to PagerDuty on critical failures. Avoid: relying solely on driver logs (hard to search at scale).

L3 β€” Scenario-Based Questions

  1. CI/CD Pipeline Design: Design an end-to-end CI/CD pipeline for a Databricks project. Include Git branching, testing, deployment to dev/staging/prod, and rollback strategy. Use DABs + Azure DevOps.

    ⚠️ Common Trap
    Approach: Git: trunk-based development (feature branches β†’ PR β†’ main). CI: on every PR β€” lint (ruff), unit test (pytest), bundle validate. CD: on merge to main β€” deploy staging β†’ integration test β†’ manual approval β†’ deploy prod. Key decisions: Service principals per environment. DABs targets for env-specific config. Rollback: git revert + redeploy, plus Delta RESTORE for data. Code: azure-pipelines.yml with stages: Validate β†’ DeployStaging β†’ DeployProd. Traps: Don't skip the manual approval gate for prod. Don't use personal tokens in CI β€” use service principals.
  2. Production Incident: Your nightly ETL job has been failing intermittently for 3 nights with SparkException: Job aborted due to stage failure. Walk through your debugging process step by step.

    ⚠️ Common Trap
    Approach: (1) Check which task/stage fails (Workflows UI), (2) Check Spark UI β€” stages tab for the failed stage, (3) Look for patterns: same stage? Same time? Same data? (4) Check executor logs for root cause: OOM? Disk full? Spot eviction? (5) Check data volume trend β€” sudden spike? Key decisions: If spot eviction β†’ increase on-demand minimum. If OOM β†’ increase memory or fix skew. If data volume β†’ scale cluster or optimize query. Traps: "Intermittent" often means spot evictions or data volume variance. Don't assume code bug if no code changed.
  3. Cost Reduction: Your team's Databricks spend increased 3x in 3 months. You need to reduce it by 40% without impacting SLAs. What do you analyze and what changes do you make?

    ⚠️ Common Trap
    Approach: (1) Analyze system.billing.usage β€” top 10 most expensive jobs, clusters, users. (2) Quick wins: terminate idle clusters, enforce auto-termination policies, switch All-Purpose to Job Clusters. (3) Optimize: Spot VMs for batch workers, right-size over-provisioned clusters. (4) Strategic: Serverless for bursty workloads, consolidate duplicate pipelines. Traps: Don't cut SLA-critical job resources. Don't remove all Spot retries. Profile before cutting β€” some expensive jobs may be correctly sized.
  4. Cluster Strategy: You have 50 data engineers, 20 data scientists, and 10 BI analysts. Design the cluster strategy: interactive clusters, job clusters, pools, and policies.

    ⚠️ Common Trap
    Approach: Engineers: shared interactive cluster (max 10 nodes, auto-terminate 60 min), job clusters for production. Data scientists: per-user interactive clusters (smaller, with ML runtime), GPU clusters for training (job clusters). BI: Serverless SQL Warehouse (auto-suspend, auto-scale). Key decisions: Instance pool for job clusters (reduce startup). Cluster policies per team (engineers get larger clusters than analysts). Traps: Don't give everyone unlimited clusters. Don't share one cluster across 80 users.
  5. Job Orchestration: You have 30 ETL jobs. 10 run hourly, 15 run daily, 5 run weekly. Some have dependencies. Design the orchestration using Databricks Workflows.

    ⚠️ Common Trap
    Approach: Group into workflow DAGs by dependency, not by schedule. Hourly: lightweight ingestion jobs (Serverless). Daily: multi-task DAGs (Bronze β†’ Silver β†’ Gold) with Job Clusters. Weekly: heavy aggregation/maintenance jobs (OPTIMIZE, VACUUM). Key decisions: Table-triggered for hourly (event-driven, not polling). Cron for daily/weekly. Use Repair Run for failed task recovery. Traps: Don't put all 30 jobs in one DAG. Group by domain and dependency. Don't schedule all daily jobs at the same time β€” stagger to avoid resource contention.
  6. OOM Debugging: A Spark job processing a 5 TB dataset fails with OOM after running for 3 hours. You have 30 minutes to fix it before the business deadline. Walk through your approach.

    ⚠️ Common Trap
    Approach: (1) Check Spark UI: driver OOM or executor OOM? (2) Driver OOM: find collect() or toPandas() in code β†’ replace with distributed processing. (3) Executor OOM: check for skew (one task processing disproportionate data) β†’ AQE or salting. (4) Quick fix: increase spark.executor.memory or add more workers to reduce per-task data size. (5) Repartition to increase partition count: spark.sql.shuffle.partitions = 2000. Traps: Don't just double the cluster size β€” diagnose first. If it's skew, more workers won't help.
  7. Migration from Airflow: Your team currently uses Apache Airflow for orchestration. Management wants to migrate to Databricks Workflows. Design the migration plan and address the gaps.

    ⚠️ Common Trap
    Approach: (1) Inventory all Airflow DAGs, identify Databricks-only vs cross-platform, (2) Databricks-only DAGs β†’ migrate to Workflows (DABs YAML), (3) Cross-platform DAGs β†’ keep in Airflow OR refactor to use Workflows + REST API for external systems, (4) Migrate incrementally: one DAG at a time, run in parallel during transition. Key decisions: Airflow's strength is multi-platform β€” if you orchestrate non-Databricks systems, keep Airflow as outer orchestrator. Traps: Don't force-migrate cross-platform DAGs β€” Workflows can't natively trigger Snowflake or external APIs.
  8. Multi-Environment Deployment: Design a deployment strategy where the same code deploys to dev (small data, small clusters), staging (prod-like), and prod (full scale) using DABs.

    ⚠️ Common Trap
    Approach: DABs targets section with per-environment overrides: dev (2 workers, sample data), staging (8 workers, prod data subset), prod (20 workers, full data). Same notebooks, same YAML structure. Different: workspace URL, cluster size, service principal, secret scope. Code: databricks.yml with targets: { dev: {...}, staging: {...}, prod: {...} }. Traps: Don't use the same secret scope across environments (dev shouldn't access prod secrets). Don't skip staging β€” it catches issues before prod.
  9. Data Pipeline SLA: Your Gold table must be refreshed by 6 AM every day. The pipeline takes 2-4 hours depending on data volume. Design the reliability strategy: monitoring, alerting, retry, fallback.

    ⚠️ Common Trap
    Approach: Schedule at 1 AM (worst-case buffer: 5 hours). Retry: 2 retries with 15-min backoff. Alert at 4:30 AM if not complete (30-min warning before SLA). Fallback: if pipeline fails after retries, serve yesterday's Gold table (stale but available). Key decisions: Monitor pipeline duration trend β€” if it's creeping toward 4 hours, proactively optimize before SLA breach. Traps: Don't schedule at 4 AM with a 2-hour pipeline β€” no buffer for retries or volume spikes.
  10. Runaway Costs: A data scientist launched an interactive cluster with 100 nodes and forgot to terminate it. It ran for 72 hours. How do you prevent this from happening again?

    ⚠️ Common Trap
    Approach: (1) Cluster policy: max 20 workers for interactive clusters, (2) Mandatory auto-termination: 60 min idle, (3) Budget alerts: per-user and per-team spend alerts, (4) Cost review: weekly scan of system.billing.usage for anomalies. Key decisions: Policies prevent at creation time, auto-termination prevents at runtime, budget alerts catch what policies miss. Traps: Don't blame the individual β€” fix the system. Without guardrails, anyone can make this mistake.
  11. Spark Debugging: A join between two large tables is taking 6 hours instead of the expected 30 minutes. The Spark UI shows massive shuffle spill to disk. Diagnose and fix.

    ⚠️ Common Trap
    Approach: (1) Check join strategy in SQL tab β€” SortMergeJoin on both sides? (2) Check task distribution β€” skewed? (3) If one table is <2 GB, force broadcast: spark.sql.autoBroadcastJoinThreshold = 2147483647. (4) If skewed, enable AQE skew join. (5) Increase spark.executor.memory to reduce spill. (6) Repartition both tables on join key before join. Traps: Shuffle spill = data doesn't fit in memory. More workers helps (more total memory), but fixing skew or changing join strategy is better.
  12. Notebook to Production: A data scientist built a prototype in a notebook. You need to productionize it. Describe the steps: refactoring, testing, CI/CD, monitoring.

    ⚠️ Common Trap
    Approach: (1) Refactor: extract transformation logic into Python modules (src/transforms/), (2) Test: write unit tests for extracted functions, (3) Parameterize: replace hardcoded values with widgets/config, (4) Error handling: add try/except, logging, alerting, (5) CI/CD: DABs + Azure DevOps, (6) Monitoring: data quality expectations, duration tracking, alerting on failure. Traps: Don't just "schedule the notebook" β€” that's not productionization. Extract, test, parameterize, monitor.
  13. DABs Project Setup: You are starting a new project with 3 DLT pipelines, 10 Workflows, and shared libraries. Design the DABs project structure, bundle configuration, and deployment targets.

    ⚠️ Common Trap
    Approach: Structure: databricks.yml (root bundle), resources/pipelines/ (3 DLT pipeline YAMLs), resources/jobs/ (10 workflow YAMLs), src/lib/ (shared modules), src/notebooks/ (pipeline notebooks), tests/. Targets: dev, staging, prod with size overrides. Shared libraries installed as wheel files on clusters. Traps: Don't put all 13 resource definitions in one YAML file β€” split into logical files using include patterns.
  14. Rollback Strategy: A production deployment introduced a bug that corrupted the Silver layer. Design the rollback process: code rollback, data recovery, and communication plan.

    ⚠️ Common Trap
    Approach: (1) Code: git revert the bad commit, redeploy previous version via DABs, (2) Data: RESTORE TABLE silver_table TO VERSION AS OF , (3) Re-run pipeline from Bronze (which is append-only and intact), (4) Validate: compare restored Silver to expected results, (5) Communicate: notify stakeholders of data delay and ETA. Traps: Don't just fix forward β€” RESTORE first to stop corruption from spreading to Gold. Don't forget to check if Gold was also affected.
  15. Monitoring Dashboard: Design a production monitoring dashboard for 50 Databricks pipelines. What metrics do you track? What alerting thresholds do you set? What tools do you use?

    ⚠️ Common Trap
    Approach: Metrics: (1) Job success/failure rate (per pipeline, daily trend), (2) Duration vs SLA (actual vs target), (3) Data freshness (last update timestamp per Gold table), (4) Data quality (expectation pass rates), (5) Cost (DBU per pipeline per day). Tools: DBSQL dashboard for visualization, PagerDuty for critical alerts, Slack for informational. Thresholds: alert if duration >2x average, if failure rate >10%, if data >1 hour stale. Traps: Don't alert on everything β€” alert fatigue is worse than no alerts. Tier alerts: critical (page) vs warning (Slack) vs informational (dashboard).
Advanced

Databricks Scenarios and Labs

#

Databricks Scenarios and Labs

Answer First: Use each scenario as an evidence-led exercise: state the contract, isolate the failure domain, inspect platform signals, apply the smallest safe change, and verify recovery.

Memory Map: contract -> symptom -> evidence -> hypothesis -> safe change -> verification.

Delta ownership: This module keeps Databricks integration context concise. Transaction-log, MERGE, time-travel, CDF, and maintenance internals are canonical on /learn/delta.

BONUS: CROSS-CUTTING / FREQUENTLY ASKED "REAL INTERVIEW" QUESTIONS

These are questions that appeared repeatedly across Glassdoor reports, Medium articles, and interview forums for 2025-2026 Senior Data Engineer roles.

Answer First: Lakehouse combines the best of both: cheap storage of a data lake (open formats like Parquet/Delta) + ACID transactions and governance of a data warehouse. Data Lake = cheap but unreliable (no transactions).

Memory Map: Top 20 Most Frequently Asked -> frequency ranking selects twenty core prompts -> topic balance prevents narrow preparation -> timed responses practice decision-first structure -> rubric scores mechanism and evidence [DB_05_QUESTION_BANK_ALL_LEVELS.md:712].

Top 20 Most Frequently Asked

  1. What is the Lakehouse architecture and how does it differ from a Data Lake and a Data Warehouse? (L1)

    Lakehouse combines the best of both: cheap storage of a data lake (open formats like Parquet/Delta) + ACID transactions and governance of a data warehouse. Data Lake = cheap but unreliable (no transactions). Data Warehouse = reliable but expensive (proprietary formats, data duplication). Lakehouse = reliable + cheap + open.

  2. Explain the Delta Lake transaction log and how it provides ACID guarantees. (L2)

    Databricks jobs read and write committed Delta snapshots, but transaction actions, snapshot reconstruction, and conflict detection are Delta-owned internals. See the canonical transaction-log and ACID answer.

  3. Design a Medallion Architecture for [specific domain]. Walk through Bronze, Silver, Gold. (L3)

    ⚠️ Common Trap
    Framework: Bronze = raw ingestion (append-only, source-specific tables, Auto Loader, no transformation). Silver = cleansed + conformed (MERGE for dedup, SCD Type 2 for dimensions, schema enforcement, quality expectations). Gold = business aggregations (domain-specific, materialized views, optimized for BI). Key decisions: what goes in each layer, how to handle late data, quality expectations per layer. Traps: Don't skip Silver — going Bronze→Gold directly is fragile.
  4. How does MERGE INTO work in Delta Lake? What are its performance pitfalls? (L2)

    Databricks schedules and monitors the workload; Delta owns match cardinality, clause semantics, scan pruning, and file-layout effects. See the canonical MERGE performance answer.

  5. Implement SCD Type 2 using MERGE INTO. (L3)

    Databricks owns the surrounding pipeline, tests, and run evidence; Delta owns the deterministic close-and-insert transition. See the canonical SCD Type 2 MERGE answer.

  6. What is Unity Catalog and how does it improve governance over Hive Metastore? (L2)

    Key points: UC provides: centralized governance across workspaces (HMS is per-workspace), fine-grained access control (HMS is coarse), data lineage (HMS has none), Delta Sharing (HMS can't share externally), column masking and row security (HMS can't), audit logging (HMS limited). Migration challenge: table references change from db.table to catalog.schema.table.

  7. How do you handle CDC from a legacy database (Oracle/SQL Server) into Delta Lake? (L3)

    ⚠️ Common Trap
    Framework: Source: Debezium connector for log-based CDC (minimal source impact). Broker: Kafka/Event Hubs for buffering and decoupling. Bronze: Auto Loader appends raw CDC events. Silver: APPLY CHANGES INTO for SCD Type 2 or MERGE for Type 1. Gold: Materialized views. Key decisions: log-based vs timestamp-based CDC, message ordering guarantees, handling deletes. Traps: Don't forget delete handling β€” CDC must capture deletes, not just inserts/updates.
  8. Compare Auto Loader vs COPY INTO β€” when do you use each? (L2)

    Key points: Auto Loader: streaming, automatic file detection, schema evolution, RocksDB state tracking, best for ongoing ingestion. COPY INTO: batch, manual trigger, idempotent, simpler setup, best for one-time or infrequent loads. Auto Loader: millions of files, continuous, production. COPY INTO: thousands of files, ad-hoc, development. Auto Loader can also run in batch mode with trigger(availableNow=True).

  9. How do you optimize a slow-running Spark job? Walk through your debugging steps. (L3)

    ⚠️ Common Trap
    Framework: (1) Spark UI β†’ find bottleneck stage, (2) Check for skew (max vs median task duration), (3) Check execution plan (join strategy, predicate pushdown), (4) Check for small files (DESCRIBE DETAIL), (5) Check shuffle/spill metrics. Key decisions: broadcast join vs SortMerge, AQE vs manual tuning, OPTIMIZE vs repartition. Traps: Don't just add workers β€” diagnose first. Skew won't be fixed by more workers.
  10. What is Z-ORDER and when would you use it? How does Liquid Clustering improve on it? (L2)

    Databricks can schedule and observe table maintenance, but data-layout selection and file-rewrite behavior are Delta-owned. See the canonical Z-ORDER versus liquid-clustering decision.

  11. Design a GDPR-compliant data deletion pipeline in a Lakehouse. (L3)

    Databricks governance locates affected assets and orchestrates approvals, audit records, backups, and downstream deletion. Delta owns physical purge and retention safety; see the canonical right-to-erasure procedure.

  12. How do you implement CI/CD for Databricks? (L2)

    Key points: DABs for infrastructure-as-code (YAML in Git). Azure DevOps/GitHub Actions for automation. Pipeline: lint β†’ unit test β†’ validate β†’ deploy staging β†’ integration test β†’ manual approval β†’ deploy prod. Service principals for authentication. Rollback: redeploy previous Git commit. Testing pyramid: unit (pytest, no cluster) β†’ integration (staging cluster) β†’ quality (DLT expectations) β†’ performance (prod-scale benchmark).

  13. What is Photon and when should you enable it? (L1)

    Native C++ vectorized engine, 2-5x faster for SQL and DataFrame operations. Enable for: SQL-heavy workloads, MERGE operations, aggregations, joins. Don't enable for: Python UDF-heavy workloads (Photon can't accelerate UDFs), simple read-only queries on small tables (overhead not worth it). Higher DBU rate β€” only cost-effective if the speedup outweighs the rate increase.

  14. Explain the small file problem and how to solve it in Delta Lake. (L2)

    Databricks exposes workload and table-health signals, while Delta owns compaction mechanics and layout trade-offs. See the canonical small-file diagnosis and repair playbook.

  15. How do you handle schema evolution in a streaming pipeline? (L2)

    Key points: Auto Loader: cloudFiles.schemaEvolutionMode β€” addNewColumns, rescue, failOnNewColumns. Delta writes: .option("mergeSchema", "true"). Strategy: rescue mode at Bronze (never lose data), explicit schema mapping at Silver, strict schema at Gold. Always alert on schema changes, even when handled automatically. Delta column mapping for renames/drops.

  16. Your pipeline is failing intermittently in production. Walk through your debugging process. (L3)

    ⚠️ Common Trap
    Framework: (1) Triage: which task, how often, what error?, (2) Pattern analysis: same time? Same data? Correlated with volume?, (3) Check: Spark UI (OOM, skew), driver logs (first ERROR), infrastructure (spot eviction, storage throttling), (4) Fix: address root cause, add retry for transient failures, (5) Prevent: monitoring, alerting, documentation. Traps: "Intermittent" usually means spot evictions, data volume variance, or transient network issues β€” not code bugs.
  17. How do you manage costs in Databricks? What strategies have you used? (L2)

    Key points: (1) Compute: Job Clusters for prod (30-50% cheaper than All-Purpose), Serverless for bursty, Spot for workers. (2) Policies: max nodes, auto-termination, required tags. (3) Storage: OPTIMIZE + VACUUM + Predictive Optimization. (4) Queries: Photon, Liquid Clustering, caching. (5) Monitoring: system tables, budget alerts, chargeback per team. Biggest lever: switching All-Purpose to Job Clusters.

  18. What is Delta Live Tables and how does it compare to manual Structured Streaming? (L2)

    Key points: DLT (now Lakeflow Declarative Pipelines): declarative, managed orchestration, built-in expectations, automatic checkpointing and retries. Manual Structured Streaming: more control, use any library, but you manage checkpoints/retries/orchestration yourself. DLT is faster to build but less flexible. Manual streaming is more work but fully customizable. Use DLT for standard ETL; manual for complex custom logic.

  19. Design a real-time analytics pipeline on Databricks. (L3)

    ⚠️ Common Trap
    Framework: Source: Kafka/Event Hubs β†’ Bronze: Auto Loader or readStream (continuous) β†’ Silver: streaming MERGE with watermarking β†’ Gold: streaming materialized view refreshed every 5 min β†’ BI: Serverless SQL Warehouse + Power BI. Key decisions: trigger interval (latency vs cost), watermark duration (late data tolerance), cluster type (Serverless vs provisioned for 24/7). Traps: Don't use batch for <5 min latency. Don't forget watermarking for stateful operations.
  20. How do you handle data quality in a Lakehouse architecture? (L2)

    Key points: (1) DLT expectations at every layer (warn β†’ drop β†’ fail escalation), (2) Schema enforcement at Silver (reject bad types/nulls), (3) Statistical monitoring (anomaly detection on row counts, null rates, distributions), (4) Automated alerting (Slack/PagerDuty on quality failures), (5) Data contracts between producers and consumers. Quality improves from Bronze (raw) to Gold (fully validated). Monitor quality metrics as time series to detect gradual degradation.

Answer First: A managed ingestion service that provides pre-built connectors to SaaS applications (Salesforce, SAP, Workday) and databases.

Memory Map: Emerging 2025-2026 Topics (Newer Questions) -> release trends surface emerging interview themes -> official behavior anchors each new feature -> scenario comparison connects adoption trade-offs -> recency review removes obsolete claims [DB_05_QUESTION_BANK_ALL_LEVELS.md:774].

Emerging 2025-2026 Topics (Newer Questions)

  1. What is Lakeflow Connect and how does it simplify ingestion? (L1)

    A managed ingestion service that provides pre-built connectors to SaaS applications (Salesforce, SAP, Workday) and databases. Eliminates the need to build custom ingestion pipelines. Configure in UI or YAML β€” Lakeflow handles extraction, change detection, and loading into Delta tables.

  2. Explain Databricks Apps β€” what are they and when would you use them? (L1)

    A framework for building and deploying data applications directly on the Databricks platform. Build Streamlit-style UIs or APIs that read from Lakehouse tables. Use when you need to expose data to non-technical users via a web interface without setting up a separate web server.

  3. What is Genie (natural language to SQL) and how does it fit into the Databricks ecosystem? (L1)

    Genie is Databricks' natural language to SQL interface. Business users ask questions in plain English, Genie generates SQL queries against your Lakehouse tables. Fits into the ecosystem as the "last mile" between data and business users who don't know SQL. Requires well-documented tables with descriptions and tags.

  4. How do you build and deploy an LLM-powered application using Databricks? (L3)

    ⚠️ Common Trap
    Framework: (1) Data: prepare training data in Delta tables, (2) Model: fine-tune or serve a foundation model via Mosaic AI, (3) Serve: Model Serving endpoint (serverless, auto-scaling), (4) App: Databricks App or external application calls the serving endpoint, (5) Monitor: track inference latency, token usage, and cost. Key decisions: fine-tune vs RAG vs prompt engineering. Traps: Don't fine-tune when RAG is sufficient. Monitor for model drift.
  5. What is the Databricks Marketplace and how do you publish/consume data products? (L2)

    Key points: A data exchange platform where providers publish datasets and consumers discover/subscribe to them. Uses Delta Sharing under the hood. Publishing: create a listing, attach a share, set access (open or request-based). Consuming: browse marketplace, subscribe, data appears as a shared catalog. Use for: third-party data enrichment, cross-organization data sharing, monetizing data assets.

  6. How does Mosaic AI integrate with the Lakehouse for ML/AI workflows? (L2)

    Key points: Mosaic AI (formerly MLflow + Databricks ML) provides: Feature Store (features in Delta tables), Model Registry (version and stage models in Unity Catalog), Model Serving (serverless endpoints), Vector Search (for RAG applications), and AI Gateway (unified API for foundation models). Integration: features stored in Delta β†’ training on Databricks clusters β†’ model registered in UC β†’ served via endpoint β†’ monitored via system tables.

  7. What is UniForm in Delta Lake and why does it matter for interoperability? (L2)

    Key points: UniForm generates Iceberg-compatible metadata alongside Delta metadata. Same Parquet files, dual metadata. External tools that support Iceberg (Snowflake, BigQuery, Trino, Presto) can read Delta tables natively β€” no data copying or format conversion. Matters for multi-engine architectures where not every consumer uses Databricks. Breaks the "table format war" β€” use Delta for writes, let others read via Iceberg.

  8. How do you use Databricks system tables for cost monitoring and optimization? (L3)

    ⚠️ Common Trap
    Framework: (1) system.billing.usage β†’ DBU cost per job, cluster, warehouse, (2) Build DBSQL dashboard: "Top 10 expensive jobs this week", "Cost trend by team", "Idle cluster hours", (3) Set budget alerts: per-team thresholds, (4) Optimization: find jobs with high DBU-per-row ratios (inefficient), find clusters with <20% utilization (oversized). Code: SELECT sku_name, SUM(usage_quantity) FROM system.billing.usage GROUP BY 1 ORDER BY 2 DESC. Traps: System tables have a delay β€” not real-time. Use for trend analysis, not live monitoring.
  9. Explain Serverless compute for jobs β€” how does it differ from provisioned clusters? (L2)

    Key points: Serverless: Databricks manages the infrastructure β€” instant startup (seconds), auto-scale, auto-terminate, no cluster configuration. Provisioned: you configure VM type, worker count, runtime version. Serverless: higher per-DBU rate but zero idle cost and zero startup time. Best for: short jobs, bursty workloads, development. Provisioned still better for: long-running streaming (steady state), very large clusters (100+ workers), specific hardware needs (GPU).

  10. What is Predictive Optimization and how does it automate OPTIMIZE/VACUUM/ANALYZE? (L2)

    Key points: A Databricks-managed service for Unity Catalog managed tables. Automatically: runs OPTIMIZE when small files accumulate, runs VACUUM when old files are piling up, runs ANALYZE when statistics are stale. Based on table usage patterns β€” tables queried frequently get optimized more often. Eliminates the need for manual maintenance job scheduling. Currently available only for managed tables in Unity Catalog.

QUESTION COUNT SUMMARY

TopicL1 (Direct)L2 (Mid-Level)L3 (Scenario)Total
Delta Lake20201555
ETL Pipelines20201555
Azure Platform & Governance20201555
Production & CI/CD20201555
Bonus (Cross-Cutting)β€”β€”β€”30
TOTAL808060250

Databricks β€” Confusions, Labs, Gotchas & Mock Interview

πŸ’‘ Interview Tip
Goal: After this page, you should NEVER struggle with Databricks-specific interview questions. Where to run labs: Databricks Free Edition or a full Databricks workspace. Free Edition is serverless-only and quota-limited.

Memory Map

🧠 DATABRICKS MASTERY β†’ CLUSTER-UC-WORKFLOW
DATABRICKS MASTERYCLUSTER-UC-WORKFLOW
────────────────────────────────────────
CCluster types (All-Purpose / Job / SQL / Serverless)
LLanguages + notebooks (magic commands, widgets)
UUnity Catalog (3-level namespace, governance)
WWorkflows (Jobs vs DLT pipelines)
FFile systems (DBFS vs workspace vs Volumes)
IIntegrations (DLT, Photon, Auto Loader, MLflow)
XCost Xplained (DBU, autoscaling, spot)

SECTION 0: TOP 8 DATABRICKS CONFUSIONS β€” Cleared Forever

Answer First: All-purpose compute supports interactive collaboration, job compute supplies isolated ephemeral production runs, and SQL warehouses serve BI and SQL concurrency. Choose among them by workload lifecycle, latency, isolation, and utilization; serverless shifts infrastructure management to Databricks.

Memory Map: Confusion 1: All-Purpose vs Job vs SQL Warehouse Cluster -> interactive capacity favors exploration and collaboration -> ephemeral capacity isolates scheduled runs -> managed SQL capacity handles BI concurrency -> latency isolation and utilization drive selection [DB_07_Confusions_Labs_MockInterview.md:28].

Confusion 1: All-Purpose vs Job vs SQL Warehouse Cluster

TypePurposeCostAuto-terminateWhen to use
All-PurposeInteractive notebooksπŸ’°πŸ’°πŸ’° highYes (configurable)Development, ad-hoc analysis
Job ClusterScheduled ETL jobsπŸ’° low (up to 50% cheaper)YES β€” dies when job endsProduction batch jobs
SQL WarehouseBI / SQL queriesπŸ’°πŸ’° mediumYesPower BI, Tableau, SQL analysts
ServerlessInstant start, no infraπŸ’°πŸ’° mediumAutoFast startup, no cluster management

Memory trick:

All-Purpose = "Playground" β€” exploration, multi-user
Job Cluster = "Assembly Line" β€” scheduled, dies after use
SQL Warehouse = "BI Gateway" β€” always-available for dashboards
Serverless = "Rent-a-cluster" β€” Databricks manages everything

Interview trap: "Why use Job Cluster over All-Purpose for production?" Answer: "Job clusters terminate after the job finishes, so you pay only for compute actually used. They also run a SINGLE job, which isolates resources β€” no noisy neighbors. Pricing is also lower per DBU for Jobs workloads."

Answer First: Use Unity Catalog tables for governed tabular data, Volumes for governed files, and Workspace Files for versioned code or small configuration assets.

Memory Map: Confusion 2: DBFS vs Workspace Files vs Volumes -> workspace files store development artifacts -> DBFS paths expose legacy distributed storage -> Unity Catalog volumes govern non-tabular files -> access and lifecycle needs select location [DB_07_Confusions_Labs_MockInterview.md:50].

Confusion 2: DBFS vs Workspace Files vs Volumes

🧠 DBFS β†’ deprecated filesystem, mounted cloud storage
DBFSdeprecated filesystem, mounted cloud storage
Workspace Files→small config files, notebooks, libraries (git repo)
Volumes→Unity Catalog governed data directory (MODERN — use this)

Which to use:

ForUse
Parquet/Delta tablesUnity Catalog managed tables
CSVs, JSON raw filesVolumes (UC-governed)
Python scripts, configsWorkspace Files (in git repo)
ML modelsUC managed tables or Volumes

Interview trap: "Why move off DBFS?" Answer: "DBFS has no fine-grained access control, no audit lineage, and no governance. Volumes give you Unity Catalog's 3-level namespace, row/column masking, lineage tracking, and per-user audit logs."

Answer First: Non-declarative logic (complex branching, external APIs)

Memory Map: Confusion 3: Jobs vs Delta Live Tables (DLT) -> imperative jobs support arbitrary code and external calls -> declarative pipelines manage table dependencies and quality -> ownership model changes monitoring and retries -> control-flow needs determine the service [DB_07_Confusions_Labs_MockInterview.md:71].

Confusion 3: Jobs vs Delta Live Tables (DLT)

Jobs→you write code + schedule it. You own everything.
DLTdeclarative pipelines. You describe WHAT, DLT handles HOW.

When to use DLT:

  • Medallion architecture (Bronze β†’ Silver β†’ Gold)
  • Built-in data quality (expect, expect_or_drop, expect_or_fail)
  • Auto-dependency resolution between tables
  • Auto-refreshed materialized views

When to use Jobs:

  • Non-declarative logic (complex branching, external APIs)
  • Existing PySpark code you don't want to rewrite
  • Custom orchestration

DLT example:

python β€” editable
import dlt
@dlt.table
@dlt.expect_or_drop("valid_amount", "amount > 0")
def silver_orders():
    return dlt.read("bronze_orders").filter("status = 'paid'")

Interview answer: "DLT is for declarative data pipelines with built-in lineage and quality. Jobs are for general-purpose orchestration. I'd pick DLT for medallion architectures and Jobs for anything that doesn't fit the declarative model."

Answer First: Cost: Photon clusters cost ~2x DBU, but run 3-5x faster β†’ net savings.

Memory Map: Confusion 4: Photon vs Standard Spark -> native vector operators accelerate supported SQL and DataFrame work -> JVM execution handles unsupported paths -> fallback remains correct but loses speedup -> plan nodes and timings reveal coverage [DB_07_Confusions_Labs_MockInterview.md:102].

Confusion 4: Photon vs Standard Spark

Photon = Databricks' C++ vectorized execution engine
(replaces JVM Spark execution for SQL/DataFrame ops)

Speedup (typical):

  • MERGE/UPDATE/DELETE: 3-5x faster
  • Aggregations, joins: 2-4x faster
  • Parquet scans: 2-3x faster

Cost: Photon clusters cost ~2x DBU, but run 3-5x faster β†’ net savings.

When NOT to use Photon:

  • Pure Python/pandas code (no Photon benefit)
  • RDD operations (Photon only helps DataFrame/SQL)
  • Very small workloads (startup overhead dominates)

Interview answer: "I enable Photon by default for SQL and DataFrame workloads, especially MERGE operations on large Delta tables. The 2x DBU cost is more than offset by the 3-5x speedup. I disable it only for pure Python or RDD workloads where it doesn't help."

Answer First: Auto Loader ( cloudFiles format): continuous or incremental file ingestion β€” the Databricks-preferred way.

Memory Map: Confusion 5: Auto Loader vs COPY INTO vs readStream -> notification discovery suits continuous file arrival -> idempotent SQL loading suits scheduled batches -> generic streaming lacks managed discovery state -> latency and file scale choose the ingestion path [DB_07_Confusions_Labs_MockInterview.md:125].

Confusion 5: Auto Loader vs COPY INTO vs readStream

🧠 COPY INTO β†’ SQL batch loader, idempotent (re-runs skip loaded files)
Auto Loader→file-stream ingestion, scalable, schema inference/evolution
COPY INTOSQL batch loader, idempotent (re-runs skip loaded files)
readStream→generic streaming reader (Kafka, Delta, files)

When to use:

  • Auto Loader (cloudFiles format): continuous or incremental file ingestion β€” the Databricks-preferred way
  • COPY INTO: one-off or scheduled SQL ingestion from cloud storage
  • readStream: anytime you need streaming (Kafka sources, Delta change feeds)

Auto Loader example:

python β€” editable
df = (spark.readStream
      .format("cloudFiles")
      .option("cloudFiles.format", "json")
      .option("cloudFiles.schemaLocation", "/schema")
      .load("/raw/events/"))

Interview trap: "Why Auto Loader over a simple file read?" Answer: "Auto Loader scales to millions of files via cloud notifications (SQS/EventGrid) instead of listing β€” that's critical on S3 where LIST is expensive and rate-limited. It also handles schema evolution and tracks already-processed files automatically."

Answer First: Managed (preferred default in UC): simpler governance, auto-optimize handles layout.

Memory Map: Confusion 6: Managed vs External Tables (Unity Catalog) -> catalog-owned storage receives managed lifecycle services -> external registration leaves data lifecycle with the owner -> dropping metadata has different file effects -> location and retention checks prove responsibility [DB_07_Confusions_Labs_MockInterview.md:152].

Confusion 6: Managed vs External Tables (Unity Catalog)

MANAGED table:
Databricks owns the data AND metadata.
DROP TABLEdeletes both files AND metadata. DATA IS GONE.
EXTERNAL table:
You own the data, Databricks owns metadata.
DROP TABLEremoves metadata only. FILES REMAIN on storage.

Syntax:

sql
-- Managed
CREATE TABLE orders (id INT) USING DELTA;

-- External (explicit LOCATION)
CREATE TABLE orders (id INT)
USING DELTA
LOCATION 'abfss://container@acct.dfs.core.windows.net/orders/';

When to use:

  • Managed (preferred default in UC): simpler governance, auto-optimize handles layout
  • External: shared with other systems (Synapse, Fabric), data already exists, legacy migration

Gotcha: In Unity Catalog, prefer managed tables β€” Predictive Optimization doesn't work on external tables.

Answer First: Unity Catalog names objects as catalog.schema.table: the catalog is the top governance boundary, the schema groups related assets, and the table is the governed data object.

Memory Map: Confusion 7: Unity Catalog Namespace Levels -> catalog names the top governance boundary -> schema groups related assets -> object name identifies the table view or volume -> three-part references remove workspace ambiguity [DB_07_Confusions_Labs_MockInterview.md:183].

Confusion 7: Unity Catalog Namespace Levels

<catalog>.<schema>.<table>
↑ ↑ ↑
β”‚ β”‚ └─ the table (like before)
β”‚ └─ schema = "database" in old Hive metastore
└─ catalog = NEW LEVEL, groups schemas by environment/team
Example: prod_catalog.sales.orders
dev_catalog.sales.orders
analytics_catalog.ml_features.customer_embeddings

Why 3 levels? You can separate environments (dev/staging/prod) or business units at the catalog level β€” without duplicating schemas.

Interview trap: "How does this compare to old Hive metastore?" Answer: "Hive metastore was 2-level: database.table. Unity Catalog adds the catalog level, which lets you group schemas by environment, team, or region, and apply permissions at the catalog level once instead of per-table."

Answer First: Want Git-based deployment via Databricks Asset Bundles

Memory Map: Confusion 8: Databricks Workflows vs Azure Data Factory / Airflow -> native orchestration integrates jobs clusters and task values -> external orchestrators coordinate broader systems -> dependency and ownership boundaries determine control -> run history and retry behavior expose tradeoffs [DB_07_Confusions_Labs_MockInterview.md:204].

Confusion 8: Databricks Workflows vs Azure Data Factory / Airflow

Databricks Workflows→native, tightly integrated, best for Databricks-only pipelines
ADF / Airflow→cross-system orchestration, multi-tool pipelines

When to use Workflows:

  • Pipeline is entirely inside Databricks
  • Need tight Spark UI integration
  • Want Git-based deployment via Databricks Asset Bundles

When to use ADF/Airflow:

  • Pipeline spans multiple systems (SQL Server β†’ Databricks β†’ Power BI refresh)
  • Complex conditional logic / external triggers
  • Already have Airflow infra

Interview answer: "Databricks Workflows for Databricks-native pipelines β€” they're cheaper (no separate orchestrator cost) and natively log into the Databricks UI. ADF or Airflow for cross-system orchestration where Databricks is one of many steps."

APPENDIX: LEARN BY DOING

Answer First: Databricks Free Edition runs these learning exercises on quota-limited, serverless-only compute; it does not provide user-configured classic clusters.

Memory Map: LAB 1 β€” Use Free Edition Serverless Compute + Run a First Notebook -> serverless environment removes cluster provisioning -> notebook attaches to managed compute -> sample transformation validates runtime access -> quota-aware cleanup completes the exercise [DB_07_Confusions_Labs_MockInterview.md:229].

LAB 1 β€” Use Free Edition Serverless Compute + Run a First Notebook

πŸ“ Note
Databricks Free Edition: Free Edition replaced the retired Community offering. It provides serverless-only notebook compute, one SQL warehouse limited to 2X-Small, at most five concurrent job tasks, restricted outbound networking, and no SLA. Check the current official limits before a lab.
python β€” editable
# STEP 1: Verify the serverless notebook session with supported Spark Connect APIs.
print("Spark version:", spark.version)
spark.range(3).selectExpr("id", "id * 10 AS value").show()
# Free Edition manages compute size and lifecycle; there is no cluster to configure.
# STEP 2: Use notebook language cells that work with serverless compute.
# %sql runs SQL; %python runs Python.
# Prefer Unity Catalog tables, volumes, or workspace files. DBFS access is limited,
# and shell/host assumptions from classic compute are not a Free Edition contract.
sql
%sql
-- STEP 3: Create a Delta table using SQL
CREATE TABLE IF NOT EXISTS demo_orders (
    id INT,
    customer STRING,
    amount DOUBLE
) USING DELTA;

INSERT INTO demo_orders VALUES (1, 'Alice', 100), (2, 'Bob', 200);
SELECT * FROM demo_orders;
python β€” editable
# STEP 4: Read that same table from Python
df = spark.table("demo_orders")
df.show()
# +---+--------+------+
# | id|customer|amount|
# +---+--------+------+
# |  1|   Alice| 100.0|
# |  2|     Bob| 200.0|
# +---+--------+------+

# See the table's history (Delta feature)
spark.sql("DESCRIBE HISTORY demo_orders").show(truncate=False)
# Shows version, timestamp, operation (CREATE, WRITE), metrics, user

🎯 Lesson: SQL and Python share the same metastore. Any table you create in SQL is instantly queryable from Python and vice versa.

Answer First: 🎯 Lesson: Auto Loader tracks which files it's already processed (in the checkpoint) so re-runs only process NEW files. It also handles schema evolution automatically.

Memory Map: LAB 2 β€” Auto Loader for Streaming File Ingestion -> cloudFiles discovers previously unseen objects -> checkpoint records processed paths -> incremental query parses new arrivals -> rerun confirms files are not duplicated [DB_07_Confusions_Labs_MockInterview.md:292].

LAB 2 β€” Auto Loader for Streaming File Ingestion

python β€” editable
# STEP 1: Set up input & output paths
input_path  = "/tmp/lab_input/events"
output_path = "/tmp/lab_output/events_delta"
schema_path = "/tmp/lab_output/schema"
checkpoint  = "/tmp/lab_output/checkpoint"

dbutils.fs.rm(input_path,  recurse=True)
dbutils.fs.rm(output_path, recurse=True)
dbutils.fs.rm(schema_path, recurse=True)
dbutils.fs.rm(checkpoint,  recurse=True)

# STEP 2: Drop a first JSON file
dbutils.fs.put(f"{input_path}/batch1.json",
    '{"id": 1, "event": "login"}\n{"id": 2, "event": "click"}', True)

# STEP 3: Start Auto Loader stream
from pyspark.sql.functions import current_timestamp

stream = (spark.readStream
          .format("cloudFiles")
          .option("cloudFiles.format", "json")
          .option("cloudFiles.schemaLocation", schema_path)
          .load(input_path)
          .withColumn("ingested_at", current_timestamp()))

query = (stream.writeStream
         .format("delta")
         .option("checkpointLocation", checkpoint)
         .trigger(availableNow=True)     # ← process available files then stop
         .start(output_path))

query.awaitTermination()
python β€” editable
# STEP 4: Check what got ingested
spark.read.format("delta").load(output_path).show()
# +---+-----+-----------------------+
# | id|event|ingested_at            |
# +---+-----+-----------------------+
# |  1|login|2026-04-05 07:00:12.3  |
# |  2|click|2026-04-05 07:00:12.3  |
# +---+-----+-----------------------+
python β€” editable
# STEP 5: Drop a SECOND file and re-run
dbutils.fs.put(f"{input_path}/batch2.json",
    '{"id": 3, "event": "purchase", "amount": 99.99}', True)

# Auto Loader DETECTS the new field "amount" (schema evolution)
# Re-run the stream β€” it picks up ONLY the new file
query = (stream.writeStream
         .format("delta")
         .option("checkpointLocation", checkpoint)
         .option("mergeSchema", "true")   # ← allow new columns
         .trigger(availableNow=True)
         .start(output_path))

query.awaitTermination()

spark.read.format("delta").load(output_path).show()
# id | event    | ingested_at | amount
#  1 | login    | ...         | NULL      ← backfilled as NULL
#  2 | click    | ...         | NULL
#  3 | purchase | ...         | 99.99     ← new column added

🎯 Lesson: Auto Loader tracks which files it's already processed (in the checkpoint) so re-runs only process NEW files. It also handles schema evolution automatically.

Answer First: Interview usage: When asked "how would you size a cluster for X TB of data", walk through this calculation out loud.

Memory Map: LAB 3 β€” Cluster Sizing Calculator (run locally) -> input bytes and target partition size estimate task count -> cores determine concurrent waves -> memory per task bounds executor sizing -> duration and utilization refine the estimate [DB_07_Confusions_Labs_MockInterview.md:367].

LAB 3 β€” Cluster Sizing Calculator (run locally)

πŸ’‘ Interview Tip
This is a thought experiment + calculator for interview prep.
🧠 Memory Map
Given data volume, compute cluster size:
───── INPUTS ─────
Total data size: 500 GB
Daily processing time: 2 hours
Cores per worker: 16
Memory per worker: 64 GB
Shuffle partitions target: ~100-200 MB per partition
──────────────────
───── CALCULATIONS ─────
Partitions for 500 GB:
500,000 MB / 128 MB = ~3900 partitions (ideal)
Parallelism needed:
Use 2-4 tasks per core for good utilization
3900 partitions / 4 tasks per core = 975 cores
975 cores / 16 cores per worker = ~60 workers
Memory check:
Executor memory: 64 GB Γ— 0.6 (storage/execution) = ~38 GB usable
60 workers Γ— 38 GB = ~2.3 TB total usable memory
500 GB data fits ~4x in memory→safe for shuffles/caching
Cost estimate:
60 workers Γ— 16 cores Γ— $0.10/DBU-hour β‰ˆ $96/hour
2 hours daily = $192/day = $5,760/month
───── RIGHT-SIZE DECISION ─────
If job finishes in 30 min with these workers→try 30 workers
If job OOMs→increase workers OR use memory-optimized nodes
If shuffles spill a lot→increase spark.sql.shuffle.partitions

Interview usage: When asked "how would you size a cluster for X TB of data", walk through this calculation out loud.

VISUAL ANIMATION 1 β€” DBU Pricing Flow

🧠 DBU = Databricks Unit = measure of compute consumption
WHAT IS A DBU?
DBUDatabricks Unit β†’ measure of compute consumption
Different cluster types have different DBU rates:
Cluster Type Cost per DBU (approx)
───────────────── ─────────────────────
All-Purpose $0.55
Jobs $0.22 ← ~60% cheaper!
SQL Warehouse $0.22
Photon uplift +2x on above
Serverless tier-dependent
COST FORMULA
Cost = DBU-rate Γ— DBUs/hour Γ— hours
+ VM costs (EC2/Azure/GCP passthrough)
EXAMPLE
i3.xlarge worker = 1 DBU/hour
20 workers Γ— 1 DBU Γ— $0.22 (Jobs) = $4.40/hour for DBUs
+ 20 Γ— $0.312/hour (EC2 cost) = $6.24/hour for VMs
─────────────────
$10.64/hour total
SAVINGS LEVERS
1. Job clusters (not All-Purpose) β†’ ~60% saving
2. Spot instances for workers→~70% saving on VM
3. Autoscaling→save idle time
4. Auto-terminate→stop paying when idle
5. Photon for DataFrame/SQL workloads→3-5x faster = less time

VISUAL ANIMATION 2

πŸ—‚οΈACCOUNT
Metastore (one per region per account)
Catalogs
prod_catalog ← Level 1: CATALOG
sales (schema) ← Level 2: SCHEMA
orders (table) ← Level 3: TABLE
customers (table)
raw_volume (volume) ← Volumes live here too
marketing (schema)
campaigns (table)
dev_catalog ← separate env, same schema names
sales (schema)
orders (table)
shared_catalog ← cross-env data, reference
reference (schema)
countries (table)
PERMISSIONS FLOW:
USAGE on prod_catalog β†’ can see catalog
USAGE on prod_catalog.sales β†’ can see schema
SELECT on prod_catalog.sales.orders β†’ can read table
πŸ’‘ Grant once at catalog level to grant to all its schemas.
Grant once at schema level to grant to all its tables.

VISUAL ANIMATION 3 β€” Medallion Architecture

πŸ“ Architecture Diagram
SOURCE SYSTEMS (Oracle, Kafka, files, APIs)
        β”‚
        β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  BRONZE (raw layer)                             β”‚
β”‚  β€’ Ingest EVERYTHING, schema-on-read            β”‚
β”‚  β€’ Append-only, never DELETE                    β”‚
β”‚  β€’ Keep source identifiers                      β”‚
β”‚  β€’ Table: bronze_orders_raw                     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
        β”‚  (cleaning, typing, dedup, validation)
        β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  SILVER (clean layer)                           β”‚
β”‚  β€’ Deduplicated, typed, joinable                β”‚
β”‚  β€’ SCD Type 2 applied for dims                  β”‚
β”‚  β€’ Data quality rules enforced                  β”‚
β”‚  β€’ Table: silver_orders                         β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
        β”‚  (aggregations, business logic)
        β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  GOLD (business layer)                          β”‚
β”‚  β€’ Aggregated, business-friendly                β”‚
β”‚  β€’ Materialized views for BI                    β”‚
β”‚  β€’ Optimized for fast query                     β”‚
β”‚  β€’ Table: gold_daily_revenue_by_region          β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
        β”‚
        β–Ό
CONSUMERS (Power BI, Tableau, ML models, APIs)

πŸ’‘ Each layer is a Delta table. Transformations between layers
   are tracked as DLT pipelines or scheduled Jobs.

GOTCHAS β€” The Databricks-Specific Errors

Answer First: Cause: Access token on an old DBFS mount expired.

Memory Map: DBFS mount token expiration -> expired mount credential breaks storage access -> error timing distinguishes token failure from missing data -> credential rotation restores trusted requests -> direct external locations avoid hidden mount state [DB_07_Confusions_Labs_MockInterview.md:525].

Gotcha 1: DBFS mount token expiration

Error: IOException: OAuth token expired / Unauthorized

Cause: Access token on an old DBFS mount expired. Fix: Re-create the mount, OR move to Unity Catalog Volumes (no mounts needed).

Answer First: This error means the resolved path or catalog object does not expose valid Delta metadata. Verify the identifier, storage location, and permissions before choosing conversion or correcting the reference.

Memory Map: "Table is not a Delta table" after converting from Parquet -> resolved path must contain a valid transaction directory -> catalog registration must point at that root -> conversion creates metadata over existing Parquet -> detail command confirms recognized format [DB_07_Confusions_Labs_MockInterview.md:536].

Gotcha 2: "Table is not a Delta table" after converting from Parquet

Confirm the fully qualified table name, resolved storage location, permissions, and current file format. If the files are genuinely Parquet, follow the canonical Delta conversion and metadata guidance; if they were already converted, repair the stale catalog or path reference instead of converting again.

Answer First: dbutils is injected by the Databricks runtime and is not a portable Spark API. Pass configuration and storage access through standard Spark, cloud SDK, or application interfaces when code must run locally or through spark-submit.

Memory Map: dbutils not available in spark-submit / local dev -> notebook runtime injects the utility object -> submitted or local processes lack that context -> standard SDK or core APIs replace helper calls -> environment parity test prevents deployment failure [DB_07_Confusions_Labs_MockInterview.md:552].

Gotcha 3: dbutils not available in spark-submit / local dev

python β€” editable
# dbutils only works inside Databricks runtime.
# Workaround for local dev:
try:
    dbutils
except NameError:
    from pyspark.sql import SparkSession
    # use local file APIs instead
    import os
    files = os.listdir(path)

Answer First: Cause: Job config issue, cluster couldn't start, or timeout misconfigured.

Memory Map: Job failing silently β€” no error in UI -> run state may fail before task execution -> cluster events reveal startup or policy rejection -> timeout and notification settings expose silent termination -> direct logs restore actionable alerting [DB_07_Confusions_Labs_MockInterview.md:568].

Gotcha 4: Job failing silently β€” no error in UI

Cause: Job config issue, cluster couldn't start, or timeout misconfigured. Fix: Check the Cluster event log in the Jobs UI β†’ "Cluster" tab. Most startup failures show there, not in the task log.

Answer First: display() is an interactive preview and can truncate the rendered result; it is not proof of total row count or completeness. Use count, aggregations, persisted outputs, or downloaded query results for validation.

Memory Map: display() cuts off rows beyond 1000 -> display limits interactive rendering rather than dataset size -> Spark result remains distributed and complete -> count or persisted output measures all rows -> production code avoids UI-only assertions [DB_07_Confusions_Labs_MockInterview.md:575].

Gotcha 5: display() cuts off rows beyond 1000

python β€” editable
# display() caps at 1000 rows by default
display(big_df)

# Fix: set display limit explicitly OR use show()
big_df.show(n=5000, truncate=False)
# OR
display(big_df.limit(5000))

Answer First: Common causes: RDD operations, some UDFs, specific formats.

Memory Map: Photon disabled warnings in logs -> unsupported RDD UDF or format forces fallback -> warning identifies the affected operator -> plan shows where native execution stops -> rewritten expressions restore acceleration when possible [DB_07_Confusions_Labs_MockInterview.md:589].

Gotcha 6: Photon disabled warnings in logs

WARNING: Photon disabled: <feature> not supported

Common causes: RDD operations, some UDFs, specific formats. Fix: Rewrite in DataFrame API; replace Python UDFs with Pandas UDFs or built-ins.

MOCK INTERVIEW

Answer First: First, Workspace strategy: separate workspaces for prod/staging/dev OR one workspace with catalog-level isolation in Unity Catalog.

Memory Map: "Design a production data platform for 200+ engineers on Databricks." -> environment boundaries separate development staging and production -> central catalog governs shared assets -> standardized bundles and policies scale delivery -> reliability audit and cost telemetry validate the platform [DB_07_Confusions_Labs_MockInterview.md:602].

Q1: "Design a production data platform for 200+ engineers on Databricks."

βœ… GOOD ANSWER:

πŸ“ Note
"Four pillars. First, Workspace strategy: separate workspaces for prod/staging/dev OR one workspace with catalog-level isolation in Unity Catalog. I prefer multi-workspace for hard security boundaries. Second, Unity Catalog: three catalogs (dev/staging/prod), schemas per business domain, Volumes for landing zones, with team-based access control using groups. Third, Compute strategy: Job Clusters for ETL, SQL Warehouses for BI, Serverless for ad-hoc. Photon enabled by default. Autoscaling + spot instances for 60-70% cost savings. Fourth, CI/CD: Databricks Asset Bundles for deployment, Git-based notebooks, automated testing in staging before prod. Governance via Unity Catalog audit logs + lineage."

Answer First: First, I'd open the Spark UI for a recent run and find the longest stage. Second, check for skew β€” if one task runs 10x longer than the median, that's skew, fix with AQE or salting.

Memory Map: "Our daily ETL takes 4 hours and is getting slower. Debug it." -> longest stage establishes the dominant phase -> task spread reveals skew or stragglers -> scan exchange and spill metrics identify the cause -> comparable rerun proves the correction [DB_07_Confusions_Labs_MockInterview.md:609].

Q2: "Our daily ETL takes 4 hours and is getting slower. Debug it."

βœ… GOOD ANSWER:

"Five steps. First, I'd open the Spark UI for a recent run and find the longest stage. Second, check for skew β€” if one task runs 10x longer than the median, that's skew, fix with AQE or salting. Third, look at shuffle read/write β€” if they grew a lot, the upstream data volume changed or partitioning got lost. Fourth, check the Delta table layout β€” is OPTIMIZE running? Are there thousands of small files from streaming? Z-ORDER on the join columns? Fifth, verify cluster utilization β€” if CPU is consistently < 50%, you're bottlenecked on I/O or shuffle; if > 90% and stages are queued, you need more workers."

Answer First: First, find the data: query Unity Catalog lineage to identify every table containing that passenger's ID. Second, delete it: run DELETE FROM each table WHERE passenger id = X.

Memory Map: "GDPR asks us to delete a passenger's data from every Delta table. How?" -> verified identity anchors the privacy request -> lineage finds every governed copy -> owners execute deletion and retention policy -> reconciliation and audit evidence close the request [DB_07_Confusions_Labs_MockInterview.md:616].

Q3: "GDPR asks us to delete a passenger's data from every Delta table. How?"

βœ… GOOD ANSWER:

"Three parts. First, find the data: query Unity Catalog lineage to identify every table containing that passenger's ID. Second, delete it: run DELETE FROM each table WHERE passenger_id = X. Delta DELETE is ACID and atomic. Third, purge from history: by default, Delta keeps old versions for 30 days (deletedFileRetentionDuration), which means time travel could still retrieve the deleted data. Run VACUUM to physically remove the old files once compliance window is over. Keep an audit log of deletions β€” Unity Catalog audit logs track this automatically."

Answer First: First, when the pipeline has complex non-declarative logic β€” external API calls, conditional branching, or bespoke orchestration. DLT expects you to describe transformations as tables; it's awkward for procedural flows.

Memory Map: "When would you NOT use Delta Live Tables?" -> external APIs complex branching or custom side effects require imperative control -> unsupported semantics weaken declarative ownership -> ordinary jobs retain explicit orchestration -> operational needs justify the exception [DB_07_Confusions_Labs_MockInterview.md:623].

Q4: "When would you NOT use Delta Live Tables?"

βœ… GOOD ANSWER:

"Three cases. First, when the pipeline has complex non-declarative logic β€” external API calls, conditional branching, or bespoke orchestration. DLT expects you to describe transformations as tables; it's awkward for procedural flows. Second, when you already have mature PySpark code in production β€” rewriting to DLT adds risk without clear ROI. Third, for cross-system pipelines where Databricks is one of many steps β€” Airflow or ADF orchestrates better. DLT shines for medallion architectures, quality-checked streaming, and cases where auto-lineage + built-in expect quality rules give you real value."

Answer First: First, move ETL from All-Purpose to Job clusters β€” about 60% cheaper per DBU. Second, enable Photon for DataFrame/SQL workloads β€” 3-5x speedup pays back the 2x DBU premium many times over.

Memory Map: "How do you reduce Databricks costs by 30%?" -> billing tags reveal the largest spend owners -> job and serverless capacity remove idle clusters -> policies enforce size termination and spot usage -> SLA-normalized cost confirms the target reduction [DB_07_Confusions_Labs_MockInterview.md:630].

Q5: "How do you reduce Databricks costs by 30%?"

βœ… GOOD ANSWER:

"Five concrete levers. First, move ETL from All-Purpose to Job clusters β€” about 60% cheaper per DBU. Second, enable Photon for DataFrame/SQL workloads β€” 3-5x speedup pays back the 2x DBU premium many times over. Third, use spot instances for workers (with on-demand driver for reliability) β€” up to 70% off VM costs. Fourth, set aggressive auto-termination on clusters (10-15 min idle) and autoscaling with sensible min-workers. Fifth, tag every cluster by team/project and build a cost dashboard β€” what you can't measure, you can't cut. Bonus: use Predictive Optimization for managed Delta tables to skip unnecessary OPTIMIZE runs."

FINAL READINESS CHECKLIST

  • Explain difference between All-Purpose, Job, SQL cluster, Serverless
  • Describe Unity Catalog's 3-level namespace from memory
  • Write an Auto Loader ingestion snippet from memory
  • Explain when you'd pick DLT vs Jobs
  • List 5 ways to cut Databricks costs
  • Explain Photon's speedup vs its cost
  • Draw the medallion architecture with 3 tables per layer
  • Distinguish managed vs external tables and know when to use each
  • Design a cluster for 500 GB daily ETL
  • Answer "how to delete a user's data for GDPR" end-to-end

If yes β†’ you're Databricks-interview ready.

Intermediate

Databricks Interview Questions

#

Databricks Interview Questions

Use this page as a prompt deck. It intentionally contains source wording and exact owner links only; the complete explanations, code, caveats, and labs remain with their canonical concept owners.

Answer pattern

  1. Give the direct answer first.
  2. Explain the mechanism or data/control path.
  3. Name the production trade-off or trap.
  4. Close with the evidence that would verify the claim.

Canonical Q-DBX index

Q-DBX-001: Explain the complete lifecycle of a Spark application from spark-submit to job completion.

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/01_Spark_Architecture_and_Internals.md#L24.

Q-DBX-002: What is the difference between the DAG Scheduler and the Task Scheduler?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/01_Spark_Architecture_and_Internals.md#L76.

Q-DBX-003: Explain narrow vs wide dependencies with examples. Why does this distinction matter?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/01_Spark_Architecture_and_Internals.md#L101.

Q-DBX-004: What is the Block Manager and how does it work?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/01_Spark_Architecture_and_Internals.md#L144.

Q-DBX-005: What happens when a driver fails vs when an executor fails?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/01_Spark_Architecture_and_Internals.md#L175.

Q-DBX-006: Explain the complete pipeline of Spark's Catalyst Optimizer.

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/01_Spark_Architecture_and_Internals.md#L212.

Q-DBX-007: What is Predicate Pushdown and Projection Pushdown? When do they NOT work?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/01_Spark_Architecture_and_Internals.md#L276.

Q-DBX-008: What is Adaptive Query Execution (AQE)? What problems does it solve?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/01_Spark_Architecture_and_Internals.md#L320.

Q-DBX-009: What is Whole-Stage Code Generation (CodeGen)?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/01_Spark_Architecture_and_Internals.md#L368.

Q-DBX-010: What is Cost-Based Optimization (CBO)?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/01_Spark_Architecture_and_Internals.md#L420.

Q-DBX-011: Explain Spark's Unified Memory Management model in detail.

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/01_Spark_Architecture_and_Internals.md#L468.

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/01_Spark_Architecture_and_Internals.md#L524.

Q-DBX-013: What causes OOM errors? How do you debug driver OOM vs executor OOM?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/01_Spark_Architecture_and_Internals.md#L562.

Q-DBX-014: What is Tungsten's memory management? What is sun.misc.Unsafe?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/01_Spark_Architecture_and_Internals.md#L615.

Q-DBX-015: Explain the complete shuffle process in Spark.

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/01_Spark_Architecture_and_Internals.md#L656.

Q-DBX-016: What is spark.sql.shuffle.partitions and how do you tune it?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/01_Spark_Architecture_and_Internals.md#L708.

Q-DBX-017: What is the External Shuffle Service and why is it critical?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/01_Spark_Architecture_and_Internals.md#L747.

Q-DBX-018: How do you handle the "Fetch Failed" exception?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/01_Spark_Architecture_and_Internals.md#L777.

Q-DBX-019: Explain shuffle spill. How do you detect and minimize it?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/01_Spark_Architecture_and_Internals.md#L821.

Q-DBX-020: Compare all Spark serialization options with trade-offs.

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/01_Spark_Architecture_and_Internals.md#L865.

Q-DBX-021: How does PySpark actually execute Python code? Explain the Py4J gateway architecture.

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/01_Spark_Architecture_and_Internals.md#L905.

Q-DBX-022: How does Spark achieve fault tolerance?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/01_Spark_Architecture_and_Internals.md#L977.

Q-DBX-023: What is speculative execution? When should you enable vs disable it?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/01_Spark_Architecture_and_Internals.md#L1015.

Q-DBX-024: What are Accumulators and Broadcast variables? What are the pitfalls?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/01_Spark_Architecture_and_Internals.md#L1048.

Q-DBX-025: Scenario β€” Your Spark job has 1000 tasks but only 100 executor cores. How does Spark schedule them?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/01_Spark_Architecture_and_Internals.md#L1101.

Q-DBX-026: Scenario β€” You're running a join between a 500 GB and a 50 MB table. The job is doing a Sort-Merge Join. What's wrong?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/01_Spark_Architecture_and_Internals.md#L1122.

Q-DBX-027: Scenario β€” Your job has 200 tasks, 199 finish in 2 minutes, but 1 task takes 45 minutes. What's happening?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/01_Spark_Architecture_and_Internals.md#L1165.

Q-DBX-028: Scenario β€” Your Spark job reads from S3 and is 3x slower than reading from HDFS. Why?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/01_Spark_Architecture_and_Internals.md#L1219.

Q-DBX-029: Scenario β€” You have an iterative ML algorithm that runs 100 iterations. After iteration 50, the job gets extremely slow and eventually fails with StackOverflow. Why?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/01_Spark_Architecture_and_Internals.md#L1255.

Q-DBX-030: What is spark.sql.files.maxPartitionBytes and spark.sql.files.openCostInBytes?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/01_Spark_Architecture_and_Internals.md#L1289.

Q-DBX-031: Transformation vs Action?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/01_Spark_Architecture_and_Internals.md#L1324.

Q-DBX-032: reduceByKey vs groupByKey?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/01_Spark_Architecture_and_Internals.md#L1334.

Q-DBX-033: DataFrame vs Dataset vs RDD?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/01_Spark_Architecture_and_Internals.md#L1344.

Q-DBX-034: Why lazy evaluation?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/01_Spark_Architecture_and_Internals.md#L1354.

Q-DBX-035: What triggers a new stage?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/01_Spark_Architecture_and_Internals.md#L1364.

Q-DBX-036: spark.sql.shuffle.partitions vs spark.default.parallelism?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/01_Spark_Architecture_and_Internals.md#L1374.

Q-DBX-037: What is a task, stage, job?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/01_Spark_Architecture_and_Internals.md#L1384.

Q-DBX-038: Explain data locality levels.

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/01_Spark_Architecture_and_Internals.md#L1397.

Q-DBX-039: How does explain() help?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/01_Spark_Architecture_and_Internals.md#L1414.

Q-DBX-040: What is Dynamic Partition Pruning (DPP)?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/01_Spark_Architecture_and_Internals.md#L1435.

Q-DBX-041: What are the differences between select(), withColumn(), and selectExpr()? When is each appropriate?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/02_PySpark_Advanced_Operations.md#L27.

Q-DBX-042: Why is chaining multiple withColumn() calls a performance anti-pattern? What's the fix?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/02_PySpark_Advanced_Operations.md#L74.

Q-DBX-043: Explain all types of joins in PySpark and their physical implementations.

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/02_PySpark_Advanced_Operations.md#L136.

Q-DBX-044: What is the default broadcast join threshold? What are the pitfalls of broadcast joins?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/02_PySpark_Advanced_Operations.md#L244.

Q-DBX-045: How do you handle skewed data in a join? Explain ALL techniques.

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/02_PySpark_Advanced_Operations.md#L283.

Q-DBX-046: Explain window functions. What's the difference between row_number(), rank(), and dense_rank()?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/02_PySpark_Advanced_Operations.md#L378.

Q-DBX-047: Write PySpark code to compute running total, 7-day moving average, and percentage of total β€” all in one pass.

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/02_PySpark_Advanced_Operations.md#L452.

Q-DBX-048: What is the difference between rowsBetween and rangeBetween?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/02_PySpark_Advanced_Operations.md#L516.

Q-DBX-049: Scenario β€” Find the first and last purchase per customer, plus the time between their first and second purchase.

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/02_PySpark_Advanced_Operations.md#L571.

Q-DBX-050: Explain repartition() vs coalesce(). When do you use each?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/02_PySpark_Advanced_Operations.md#L629.

Q-DBX-051: What is bucketing? How does it eliminate shuffles?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/02_PySpark_Advanced_Operations.md#L668.

Q-DBX-052: Compare Hash Partitioning vs Range Partitioning.

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/02_PySpark_Advanced_Operations.md#L714.

Q-DBX-053: What are UDFs? Why should you avoid them? What are the alternatives?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/02_PySpark_Advanced_Operations.md#L748.

Q-DBX-054: Write a Pandas UDF. When do you use SCALAR vs GROUPED_MAP vs GROUPED_AGG?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/02_PySpark_Advanced_Operations.md#L817.

Q-DBX-055: What is mapInPandas and when do you use it?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/02_PySpark_Advanced_Operations.md#L886.

Q-DBX-056: Explain cache(), persist(), and checkpoint(). When would you use each?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/02_PySpark_Advanced_Operations.md#L923.

Q-DBX-057: Explain the Structured Streaming execution model.

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/02_PySpark_Advanced_Operations.md#L972.

Q-DBX-058: What are output modes? When is each used?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/02_PySpark_Advanced_Operations.md#L1016.

Q-DBX-059: Explain watermarking with a real scenario.

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/02_PySpark_Advanced_Operations.md#L1057.

Q-DBX-060: How do stream-stream joins work? What are the requirements?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/02_PySpark_Advanced_Operations.md#L1118.

Q-DBX-061: Explain the foreachBatch pattern. When is it needed?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/02_PySpark_Advanced_Operations.md#L1167.

Q-DBX-062: How do you achieve exactly-once semantics in Structured Streaming?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/02_PySpark_Advanced_Operations.md#L1210.

Q-DBX-063: Scenario β€” Your streaming pipeline's state store is growing unbounded. How do you fix it?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/02_PySpark_Advanced_Operations.md#L1249.

Q-DBX-064: What is the difference between trigger(once=True) and trigger(availableNow=True)?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/02_PySpark_Advanced_Operations.md#L1301.

Q-DBX-065: Deduplicate records keeping the most recent per key.

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/02_PySpark_Advanced_Operations.md#L1341.

Q-DBX-066: Pivot a table β€” convert rows to columns.

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/02_PySpark_Advanced_Operations.md#L1391.

Q-DBX-067: Find gaps in a sequential series.

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/02_PySpark_Advanced_Operations.md#L1446.

Q-DBX-068: Sessionize clickstream data (gap-based sessions).

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/02_PySpark_Advanced_Operations.md#L1504.

Q-DBX-069: Flatten a deeply nested JSON structure.

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/02_PySpark_Advanced_Operations.md#L1576.

Q-DBX-070: Write a query to find the top 3 products by revenue in each category.

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/02_PySpark_Advanced_Operations.md#L1659.

Q-DBX-071: Compute the running difference between consecutive rows.

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/02_PySpark_Advanced_Operations.md#L1712.

Q-DBX-072: Find employees whose salary is above the department average.

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/02_PySpark_Advanced_Operations.md#L1753.

Q-DBX-073: Scenario β€” Given two DataFrames (orders and returns), find customers who placed orders but never returned anything.

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/02_PySpark_Advanced_Operations.md#L1805.

Q-DBX-074: Calculate month-over-month growth rate per product.

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/02_PySpark_Advanced_Operations.md#L1849.

Q-DBX-075: Find all pairs of products frequently bought together (market basket analysis).

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/02_PySpark_Advanced_Operations.md#L1895.

Q-DBX-076: Scenario β€” Process a large CSV with bad records. Keep good records, quarantine bad ones.

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/02_PySpark_Advanced_Operations.md#L1967.

Q-DBX-077: Implement a custom aggregation β€” median (not built into Spark SQL).

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/02_PySpark_Advanced_Operations.md#L2011.

Q-DBX-078: Write a streaming pipeline that reads from Kafka, deduplicates, and writes to Delta.

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/02_PySpark_Advanced_Operations.md#L2070.

Q-DBX-079: Explain all SCD Types. When would you use each?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/04_ETL_Scenarios_and_Design.md#L35.

Q-DBX-080: Implement SCD Type 1 (Overwrite) in Databricks.

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/04_ETL_Scenarios_and_Design.md#L63.

Q-DBX-081: Implement SCD Type 2 (Full History) β€” This is the #1 most asked question.

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/04_ETL_Scenarios_and_Design.md#L116.

Q-DBX-082: Explain the "Merge Key Trick" for SCD Type 2. Why is it needed?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/04_ETL_Scenarios_and_Design.md#L233.

Q-DBX-083: Implement SCD Type 2 using Delta Live Tables (DLT) β€” the modern approach.

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/04_ETL_Scenarios_and_Design.md#L289.

Q-DBX-084: Implement SCD Type 3 (Previous Value Column).

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/04_ETL_Scenarios_and_Design.md#L339.

Q-DBX-085: Scenario β€” Implement SCD Type 2 for a customer dimension with 500M records, receiving 2M daily updates. Design the solution at scale.

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/04_ETL_Scenarios_and_Design.md#L395.

Q-DBX-086: Design a CDC pipeline from Oracle/MySQL to Delta Lake.

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/04_ETL_Scenarios_and_Design.md#L473.

Q-DBX-087: What is Delta Lake Change Data Feed (CDF)? How is it different from CDC?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/04_ETL_Scenarios_and_Design.md#L565.

Q-DBX-088: Debezium CDC event types β€” what do they mean?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/04_ETL_Scenarios_and_Design.md#L620.

Q-DBX-089: Design a medallion architecture. What are the design decisions for each layer?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/04_ETL_Scenarios_and_Design.md#L646.

Q-DBX-090: When would you deviate from the standard medallion pattern?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/04_ETL_Scenarios_and_Design.md#L747.

Q-DBX-091: How do you handle data quality between medallion layers?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/04_ETL_Scenarios_and_Design.md#L765.

Q-DBX-092: What is Auto Loader? How does it work?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/04_ETL_Scenarios_and_Design.md#L818.

Q-DBX-093: How does Auto Loader handle schema evolution?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/04_ETL_Scenarios_and_Design.md#L854.

Q-DBX-094: Auto Loader vs COPY INTO β€” when do you use each?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/04_ETL_Scenarios_and_Design.md#L890.

Q-DBX-095: Q16b: How do you handle late-arriving data in streaming pipelines?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/04_ETL_Scenarios_and_Design.md#L932.

Q-DBX-096: What are the three DLT expectation levels?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/04_ETL_Scenarios_and_Design.md#L990.

Q-DBX-097: What is the difference between a streaming live table, a materialized view, and a view in DLT?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/04_ETL_Scenarios_and_Design.md#L1021.

Q-DBX-098: How do you implement SCD Type 2 in DLT? (Compare with manual approach)

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/04_ETL_Scenarios_and_Design.md#L1061.

Q-DBX-099: Scenario β€” Design a pipeline for 10 billion clickstream events/day from Kafka.

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/04_ETL_Scenarios_and_Design.md#L1098.

Q-DBX-100: Scenario β€” Your daily batch pipeline takes 8 hours. Business wants it under 2 hours. How do you optimize?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/04_ETL_Scenarios_and_Design.md#L1146.

Q-DBX-101: Scenario β€” Design a data mesh on Databricks.

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/04_ETL_Scenarios_and_Design.md#L1187.

Q-DBX-102: Scenario β€” Design a real-time fraud detection pipeline.

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/04_ETL_Scenarios_and_Design.md#L1233.

Q-DBX-103: Scenario β€” You need to backfill 2 years of historical data, then switch to incremental. Design this.

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/04_ETL_Scenarios_and_Design.md#L1293.

Q-DBX-104: Scenario β€” Your MERGE statement takes 3 hours. How do you optimize it?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/04_ETL_Scenarios_and_Design.md#L1350.

Q-DBX-105: Scenario β€” Multiple teams need access to the same data with different views. How?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/04_ETL_Scenarios_and_Design.md#L1397.

Q-DBX-106: How do you design an idempotent pipeline?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/04_ETL_Scenarios_and_Design.md#L1433.

Q-DBX-107: How do you monitor pipeline health in production?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/04_ETL_Scenarios_and_Design.md#L1478.

Q-DBX-108: How do you read a Spark execution plan? Walk through a real example.

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/05_Performance_Tuning_and_Production.md#L30.

Q-DBX-109: What are the key metrics to check in Spark UI for performance issues?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/05_Performance_Tuning_and_Production.md#L100.

Q-DBX-110: Scenario β€” You see a stage with 200 tasks, 199 complete in 30 seconds, 1 takes 40 minutes. Diagnose and fix.

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/05_Performance_Tuning_and_Production.md#L164.

Q-DBX-111: How do you determine the optimal number of partitions?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/05_Performance_Tuning_and_Production.md#L267.

Q-DBX-112: How do you handle the small file problem?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/05_Performance_Tuning_and_Production.md#L299.

Q-DBX-113: What is the difference between spark.sql.files.maxPartitionBytes and spark.sql.files.openCostInBytes?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/05_Performance_Tuning_and_Production.md#L338.

Q-DBX-114: Q6B: How do you calculate executor memory and cores for a Spark cluster?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/05_Performance_Tuning_and_Production.md#L369.

Q-DBX-115: Q6C: What is AQE (Adaptive Query Execution) and what does it auto-fix?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/05_Performance_Tuning_and_Production.md#L428.

Q-DBX-116: What is Unity Catalog? Explain the object hierarchy.

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/05_Performance_Tuning_and_Production.md#L494.

Q-DBX-117: How do you implement row-level and column-level security in Unity Catalog?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/05_Performance_Tuning_and_Production.md#L527.

Q-DBX-118: How does data lineage work in Unity Catalog?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/05_Performance_Tuning_and_Production.md#L564.

Q-DBX-119: What are Storage Credentials and External Locations?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/05_Performance_Tuning_and_Production.md#L590.

Q-DBX-120: How do you migrate from Hive Metastore to Unity Catalog?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/05_Performance_Tuning_and_Production.md#L639.

Q-DBX-121: What is the Photon engine? When does it help and when doesn't it?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/05_Performance_Tuning_and_Production.md#L694.

Q-DBX-122: Q12B: What is a shuffle and why is it the #1 performance killer?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/05_Performance_Tuning_and_Production.md#L738.

Q-DBX-123: Q12C: When should you use broadcast joins vs sort-merge joins?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/05_Performance_Tuning_and_Production.md#L770.

Q-DBX-124: Q12D: When should you cache data, and what are the tradeoffs?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/05_Performance_Tuning_and_Production.md#L803.

Q-DBX-125: Q12E: Why Parquet + Snappy, and when would you choose differently?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/05_Performance_Tuning_and_Production.md#L853.

Q-DBX-126: Q12F: What is Z-ORDER and how does Liquid Clustering improve on it?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/05_Performance_Tuning_and_Production.md#L881.

Q-DBX-127: Compare Databricks Workflows vs Apache Airflow. When would you use each?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/05_Performance_Tuning_and_Production.md#L918.

Q-DBX-128: What is the difference between Job Cluster and All-Purpose Cluster?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/05_Performance_Tuning_and_Production.md#L946.

Q-DBX-129: How do you pass parameters between tasks in a Workflow?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/05_Performance_Tuning_and_Production.md#L971.

Q-DBX-130: What are Databricks Asset Bundles (DABs)? How do they enable CI/CD?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/05_Performance_Tuning_and_Production.md#L1001.

Q-DBX-131: How do you manage costs in Databricks? What strategies have you used?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/05_Performance_Tuning_and_Production.md#L1065.

Q-DBX-132: How do you implement CI/CD for Databricks pipelines?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/05_Performance_Tuning_and_Production.md#L1118.

Q-DBX-133: How do you handle data governance for a large organization?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/05_Performance_Tuning_and_Production.md#L1182.

Q-DBX-134: How do you handle GDPR "right to be forgotten" in Delta Lake?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/05_Performance_Tuning_and_Production.md#L1231.

Q-DBX-135: How would you design a data platform on Databricks for a company with 200+ data engineers?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/05_Performance_Tuning_and_Production.md#L1277.

Q-DBX-136: Scenario β€” You're brought in to rescue a failing Databricks migration. The team has been working for 6 months but pipelines are slow, data quality is poor, and costs are 3x budget. What do you do?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/05_Performance_Tuning_and_Production.md#L1341.

Q-DBX-137: How do you approach data modeling in a lakehouse?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/05_Performance_Tuning_and_Production.md#L1401.

Q-DBX-138: Scenario β€” A production pipeline that was running fine for 6 months suddenly takes 5x longer. Nothing changed in the code. What happened?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/05_Performance_Tuning_and_Production.md#L1449.

Q-DBX-139: What is Serverless in Databricks? How does it differ from standard compute?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/05_Performance_Tuning_and_Production.md#L1511.

Q-DBX-140: Quick-fire: Common production issues and their solutions.

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/05_Performance_Tuning_and_Production.md#L1545.

Q-DBX-141: What is Medallion Architecture?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_02_ETL_Pipelines_Databricks.md#L11.

Q-DBX-142: When would you NOT follow Medallion Architecture?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_02_ETL_Pipelines_Databricks.md#L59.

Q-DBX-143: How do you handle late-arriving data?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_02_ETL_Pipelines_Databricks.md#L74.

Q-DBX-144: What is SCD (Slowly Changing Dimension)?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_02_ETL_Pipelines_Databricks.md#L115.

Q-DBX-145: SCD Type 2 β€” FULL IMPLEMENTATION (The #1 most asked question)

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_02_ETL_Pipelines_Databricks.md#L139.

Q-DBX-146: SCD Type 2 the EASY way β€” Lakeflow apply_changes

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_02_ETL_Pipelines_Databricks.md#L297.

Q-DBX-147: What is CDC? Design a pipeline: Oracle β†’ Kafka β†’ Delta Lake

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_02_ETL_Pipelines_Databricks.md#L346.

Q-DBX-148: What is Change Data Feed (CDF)? How is it different from CDC?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_02_ETL_Pipelines_Databricks.md#L421.

Q-DBX-149: What is Auto Loader?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_02_ETL_Pipelines_Databricks.md#L477.

Q-DBX-150: How does Auto Loader handle schema changes?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_02_ETL_Pipelines_Databricks.md#L529.

Q-DBX-151: Auto Loader vs COPY INTO β€” when to use which?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_02_ETL_Pipelines_Databricks.md#L561.

Q-DBX-152: What is Lakeflow? (formerly called DLT / Delta Live Tables)

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_02_ETL_Pipelines_Databricks.md#L589.

Q-DBX-153: What are Data Quality Expectations? (3 levels)

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_02_ETL_Pipelines_Databricks.md#L624.

Q-DBX-154: What is the difference between Materialized View and Streaming Table?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_02_ETL_Pipelines_Databricks.md#L668.

Q-DBX-155: What is TRIGGERED vs CONTINUOUS pipeline mode?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_02_ETL_Pipelines_Databricks.md#L713.

Q-DBX-156: Design a booking pipeline for a travel platform (10 billion events/day)

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_02_ETL_Pipelines_Databricks.md#L731.

Q-DBX-157: Scenario β€” Our MERGE on booking fact table takes 3 hours. How do you fix it?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_02_ETL_Pipelines_Databricks.md#L800.

Q-DBX-158: Scenario β€” Migrate Oracle CDC to Delta Lake for a travel platform

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_02_ETL_Pipelines_Databricks.md#L847.

Q-DBX-159: How do you design a Data Quality Framework?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_02_ETL_Pipelines_Databricks.md#L899.

Q-DBX-160: What is Medallion Architecture?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_02_Quick_Recall.md#L69.

Q-DBX-161: What goes into Bronze layer?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_02_Quick_Recall.md#L72.

Q-DBX-162: What goes into Silver layer?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_02_Quick_Recall.md#L75.

Q-DBX-163: What goes into Gold layer?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_02_Quick_Recall.md#L78.

Q-DBX-164: Who reads each layer?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_02_Quick_Recall.md#L81.

Q-DBX-165: When would you skip a layer?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_02_Quick_Recall.md#L84.

Q-DBX-166: What is late-arriving data?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_02_Quick_Recall.md#L89.

Q-DBX-167: How do you handle late-arriving data in each layer?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_02_Quick_Recall.md#L96.

Q-DBX-168: What metadata should Bronze have?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_02_Quick_Recall.md#L103.

Q-DBX-169: Medallion vs Data Mesh β€” what's the difference?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_02_Quick_Recall.md#L111.

Q-DBX-170: What is SCD?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_02_Quick_Recall.md#L149.

Q-DBX-171: What is SCD Type 1?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_02_Quick_Recall.md#L152.

Q-DBX-172: What is SCD Type 2?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_02_Quick_Recall.md#L155.

Q-DBX-173: What is SCD Type 3?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_02_Quick_Recall.md#L158.

Q-DBX-174: Why is SCD Type 2 the hardest to implement with MERGE?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_02_Quick_Recall.md#L161.

Q-DBX-175: What is the EASY way to do SCD Type 2?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_02_Quick_Recall.md#L200.

Q-DBX-176: What is CDC?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_02_Quick_Recall.md#L246.

Q-DBX-177: What is Debezium?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_02_Quick_Recall.md#L249.

Q-DBX-178: Why use Debezium instead of direct queries?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_02_Quick_Recall.md#L252.

Q-DBX-179: What is CDF (Change Data Feed)? How is it different from CDC?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_02_Quick_Recall.md#L256.

Q-DBX-180: What columns does CDF add?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_02_Quick_Recall.md#L268.

Q-DBX-181: Design a CDC pipeline (Oracle β†’ Delta Lake)

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_02_Quick_Recall.md#L278.

Q-DBX-182: How do you handle CDC deletes in Silver?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_02_Quick_Recall.md#L293.

Q-DBX-183: What if CDC events arrive out of order?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_02_Quick_Recall.md#L305.

Q-DBX-184: What is Auto Loader?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_02_Quick_Recall.md#L346.

Q-DBX-185: What are the two modes of Auto Loader?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_02_Quick_Recall.md#L349.

Q-DBX-186: What is _rescued_data?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_02_Quick_Recall.md#L353.

Q-DBX-187: How does Auto Loader handle schema evolution?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_02_Quick_Recall.md#L356.

Q-DBX-188: Auto Loader vs COPY INTO β€” when to use which?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_02_Quick_Recall.md#L359.

Q-DBX-189: What is Lakeflow (DLT)?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_02_Quick_Recall.md#L407.

Q-DBX-190: What was DLT renamed to?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_02_Quick_Recall.md#L410.

Q-DBX-191: What is a Streaming Table?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_02_Quick_Recall.md#L413.

Q-DBX-192: What is a Materialized View?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_02_Quick_Recall.md#L416.

Q-DBX-193: What are the 3 levels of Expectations?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_02_Quick_Recall.md#L419.

Q-DBX-194: What is apply_changes?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_02_Quick_Recall.md#L426.

Q-DBX-195: TRIGGERED vs CONTINUOUS mode?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_02_Quick_Recall.md#L429.

Q-DBX-196: Streaming Table vs Materialized View β€” when to use which?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_02_Quick_Recall.md#L437.

Q-DBX-197: How do Expectations work with Quarantine pattern?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_02_Quick_Recall.md#L446.

Q-DBX-198: How do you build a complete Lakeflow pipeline?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_02_Quick_Recall.md#L459.

Q-DBX-199: How would you design a booking pipeline for a travel platform?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_02_Quick_Recall.md#L492.

Q-DBX-200: MERGE takes 3 hours on fact table. How to fix?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_02_Quick_Recall.md#L515.

Q-DBX-201: How to build a Data Quality Framework?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_02_Quick_Recall.md#L523.

Q-DBX-202: What is Unity Catalog? What is the object hierarchy?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_03_Azure_Platform_Governance.md#L11.

Q-DBX-203: What is row-level and column-level security?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_03_Azure_Platform_Governance.md#L68.

Q-DBX-204: What is data lineage? How does Unity Catalog track it?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_03_Azure_Platform_Governance.md#L136.

Q-DBX-205: What are Storage Credentials and External Locations?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_03_Azure_Platform_Governance.md#L163.

Q-DBX-206: How do you migrate from Hive Metastore to Unity Catalog?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_03_Azure_Platform_Governance.md#L218.

Q-DBX-207: What is ABAC? How is it different from RBAC?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_03_Azure_Platform_Governance.md#L269.

Q-DBX-208: What is Photon Engine?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_03_Azure_Platform_Governance.md#L340.

Q-DBX-209: What is Serverless compute in Databricks?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_03_Azure_Platform_Governance.md#L388.

Q-DBX-210: What are Serverless Workspaces?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_03_Azure_Platform_Governance.md#L418.

Q-DBX-211: What is the difference between Job Cluster, All-Purpose Cluster, and Serverless?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_03_Azure_Platform_Governance.md#L444.

Q-DBX-212: How does Azure Databricks architecture work? (Three-plane model)

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_03_Azure_Platform_Governance.md#L471.

Q-DBX-213: What is Azure Key Vault integration? Why is it important?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_03_Azure_Platform_Governance.md#L535.

Q-DBX-214: What are Service Principals? Why use them for production?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_03_Azure_Platform_Governance.md#L573.

Q-DBX-215: How do you handle GDPR "Right to Be Forgotten" in Delta Lake?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_03_Azure_Platform_Governance.md#L606.

Q-DBX-216: How would you design a governance framework for 200+ data engineers?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_03_Azure_Platform_Governance.md#L665.

Q-DBX-217: What is Delta Sharing?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_03_Azure_Platform_Governance.md#L708.

Q-DBX-218: What are Multi-table Transactions?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_03_Azure_Platform_Governance.md#L750.

Q-DBX-219: What is Compatibility Mode?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_03_Azure_Platform_Governance.md#L782.

Q-DBX-220: What is Lakebase?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_03_Azure_Platform_Governance.md#L798.

Q-DBX-221: What is Predictive Optimization?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_03_Azure_Platform_Governance.md#L837.

Q-DBX-222: What is Unity Catalog?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_03_Quick_Recall.md#L67.

Q-DBX-223: What is the 3-level namespace?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_03_Quick_Recall.md#L70.

Q-DBX-224: What is a Metastore?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_03_Quick_Recall.md#L73.

Q-DBX-225: What objects does Unity Catalog manage?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_03_Quick_Recall.md#L76.

Q-DBX-226: What is row-level security?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_03_Quick_Recall.md#L79.

Q-DBX-227: What is column masking?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_03_Quick_Recall.md#L82.

Q-DBX-228: How do you implement row-level security?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_03_Quick_Recall.md#L85.

Q-DBX-229: What is data lineage?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_03_Quick_Recall.md#L92.

Q-DBX-230: What is the difference between Managed and External tables in Unity Catalog?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_03_Quick_Recall.md#L95.

Q-DBX-231: What is a Storage Credential?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_03_Quick_Recall.md#L103.

Q-DBX-232: What is an External Location?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_03_Quick_Recall.md#L106.

Q-DBX-233: How to migrate from Hive Metastore to Unity Catalog?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_03_Quick_Recall.md#L113.

Q-DBX-234: RBAC vs ABAC β€” what's the difference?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_03_Quick_Recall.md#L121.

Q-DBX-235: What is Photon?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_03_Quick_Recall.md#L163.

Q-DBX-236: When should you NOT use Photon?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_03_Quick_Recall.md#L166.

Q-DBX-237: Does Photon cost more?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_03_Quick_Recall.md#L169.

Q-DBX-238: Is Photon enabled by default?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_03_Quick_Recall.md#L172.

Q-DBX-239: What is Serverless compute?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_03_Quick_Recall.md#L208.

Q-DBX-240: What are the 3 compute types?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_03_Quick_Recall.md#L211.

Q-DBX-241: What are Serverless Workspaces?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_03_Quick_Recall.md#L216.

Q-DBX-242: Job Cluster vs All-Purpose β€” when to use which?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_03_Quick_Recall.md#L219.

Q-DBX-243: What is ADLS Gen2?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_03_Quick_Recall.md#L260.

Q-DBX-244: What is Azure Key Vault?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_03_Quick_Recall.md#L263.

Q-DBX-245: What is a Service Principal?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_03_Quick_Recall.md#L269.

Q-DBX-246: Why use Service Principal instead of personal login for production?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_03_Quick_Recall.md#L272.

Q-DBX-247: What are the 3 planes of Azure Databricks?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_03_Quick_Recall.md#L278.

Q-DBX-248: What is GDPR "Right to be Forgotten"?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_03_Quick_Recall.md#L316.

Q-DBX-249: How do you implement Right to be Forgotten in Delta Lake?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_03_Quick_Recall.md#L319.

Q-DBX-250: What is pseudonymization?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_03_Quick_Recall.md#L324.

Q-DBX-251: How do you tag PII columns in Unity Catalog?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_03_Quick_Recall.md#L327.

Q-DBX-252: What is Delta Sharing?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_03_Quick_Recall.md#L362.

Q-DBX-253: Why not just copy data to share it?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_03_Quick_Recall.md#L365.

Q-DBX-254: What is Compatibility Mode?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_03_Quick_Recall.md#L368.

Q-DBX-255: What are Multi-table Transactions?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_03_Quick_Recall.md#L371.

Q-DBX-256: What is Databricks Workflows? How does it compare to Apache Airflow?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_04_Production_CICD_MockInterview.md#L25.

Q-DBX-257: How do tasks pass data to each other in a Workflow?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_04_Production_CICD_MockInterview.md#L61.

Q-DBX-258: What are table-triggered jobs?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_04_Production_CICD_MockInterview.md#L110.

Q-DBX-259: What are Declarative Automation Bundles?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_04_Production_CICD_MockInterview.md#L148.

Q-DBX-260: How does CI/CD work with Azure DevOps?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_04_Production_CICD_MockInterview.md#L227.

Q-DBX-261: What testing strategies exist for Databricks pipelines?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_04_Production_CICD_MockInterview.md#L312.

Q-DBX-262: How do you manage costs for a large Databricks deployment?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_04_Production_CICD_MockInterview.md#L353.

Q-DBX-263: What are Spot VMs? How do they save money?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_04_Production_CICD_MockInterview.md#L418.

Q-DBX-264: How do you read a Spark execution plan?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_04_Production_CICD_MockInterview.md#L454.

Q-DBX-265: How do you debug slow jobs using Spark UI?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_04_Production_CICD_MockInterview.md#L520.

Q-DBX-266: Scenario β€” Pipeline suddenly 5x slower, no code change. What happened?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_04_Production_CICD_MockInterview.md#L565.

Q-DBX-267: Common production issues β€” quick reference

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_04_Production_CICD_MockInterview.md#L624.

Q-DBX-268: Design a data platform for a travel platform on Azure Databricks

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_04_Production_CICD_MockInterview.md#L645.

Q-DBX-269: MOCK Q1: "Tell me about your experience with Databricks and Delta Lake."

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_04_Production_CICD_MockInterview.md#L733.

Q-DBX-270: MOCK Q2: "Design a CDC pipeline from Oracle to Delta Lake for our booking system."

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_04_Production_CICD_MockInterview.md#L749.

Q-DBX-271: MOCK Q3: "How would you implement SCD Type 2 for our passenger dimension?"

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_04_Production_CICD_MockInterview.md#L767.

Q-DBX-272: MOCK Q4: "Our booking fact table MERGE takes 3 hours. How would you optimize?"

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_04_Production_CICD_MockInterview.md#L781.

Q-DBX-273: MOCK Q5: "How do you handle data governance and GDPR for passenger data?"

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_04_Production_CICD_MockInterview.md#L799.

Q-DBX-274: MOCK Q6: "Explain your CI/CD approach for Databricks pipelines."

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_04_Production_CICD_MockInterview.md#L817.

Q-DBX-275: MOCK Q7: "How would you manage costs for a large Databricks deployment?"

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_04_Production_CICD_MockInterview.md#L833.

Q-DBX-276: MOCK Q8: "What is Unity Catalog and how would you structure it for our organization?"

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_04_Production_CICD_MockInterview.md#L848.

Q-DBX-277: MOCK Q9: "A pipeline that was fine for 6 months is suddenly 5x slower. Walk me through your debugging."

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_04_Production_CICD_MockInterview.md#L865.

Q-DBX-278: MOCK Q10: "What's new in Databricks in 2025-2026 that excites you?"

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_04_Production_CICD_MockInterview.md#L882.

Q-DBX-279: MOCK INTERVIEW β€” 10 Questions (Production & CI/CD Focus)

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_04_Production_CICD_MockInterview.md#L898.

Q-DBX-280: MOCK PROD Q1: "Walk me through how you'd set up CI/CD for a Databricks project."

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_04_Production_CICD_MockInterview.md#L904.

Q-DBX-281: MOCK PROD Q2: "Your nightly ETL job failed at 3 AM. Walk me through your investigation."

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_04_Production_CICD_MockInterview.md#L960.

Q-DBX-282: MOCK PROD Q3: "How do you manage costs in a Databricks environment?"

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_04_Production_CICD_MockInterview.md#L1032.

Q-DBX-283: MOCK PROD Q4: "Compare Databricks Workflows vs Apache Airflow. When would you use each?"

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_04_Production_CICD_MockInterview.md#L1094.

Q-DBX-284: MOCK PROD Q5: "How do you handle secrets and credentials in Databricks?"

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_04_Production_CICD_MockInterview.md#L1136.

Q-DBX-285: MOCK PROD Q6: "A job that usually takes 30 minutes now takes 4 hours. What do you check?"

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_04_Production_CICD_MockInterview.md#L1194.

Q-DBX-286: MOCK PROD Q7: "How do you test notebooks before deploying to production?"

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_04_Production_CICD_MockInterview.md#L1265.

Q-DBX-287: MOCK PROD Q8: "Explain the difference between all-purpose, job, and SQL warehouse clusters."

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_04_Production_CICD_MockInterview.md#L1337.

Q-DBX-288: MOCK PROD Q9: "How do you handle a schema change in a production pipeline?"

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_04_Production_CICD_MockInterview.md#L1398.

Q-DBX-289: MOCK PROD Q10: "Design a retry strategy for a multi-task workflow with dependencies."

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_04_Production_CICD_MockInterview.md#L1465.

Q-DBX-290: What is Databricks Workflows?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_04_Quick_Recall.md#L72.

Q-DBX-291: What task types are supported?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_04_Quick_Recall.md#L75.

Q-DBX-292: What is a Repair Run?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_04_Quick_Recall.md#L78.

Q-DBX-293: How do tasks pass data to each other?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_04_Quick_Recall.md#L81.

Q-DBX-294: What is a table-triggered job?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_04_Quick_Recall.md#L90.

Q-DBX-295: Workflows vs Airflow β€” when to use which?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_04_Quick_Recall.md#L93.

Q-DBX-296: What are Declarative Automation Bundles?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_04_Quick_Recall.md#L143.

Q-DBX-297: What is the main config file?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_04_Quick_Recall.md#L146.

Q-DBX-298: What CLI commands are used?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_04_Quick_Recall.md#L149.

Q-DBX-299: How does CI/CD work with Azure DevOps?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_04_Quick_Recall.md#L157.

Q-DBX-300: Show a basic databricks.yml example

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_04_Quick_Recall.md#L167.

Q-DBX-301: What testing strategies exist for Databricks?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_04_Quick_Recall.md#L200.

Q-DBX-302: Azure DevOps pipeline YAML example

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_04_Quick_Recall.md#L207.

Q-DBX-303: What is the biggest cost in Databricks?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_04_Quick_Recall.md#L277.

Q-DBX-304: What are Spot VMs?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_04_Quick_Recall.md#L280.

Q-DBX-305: What are Cluster Policies?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_04_Quick_Recall.md#L283.

Q-DBX-306: How do you implement chargeback?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_04_Quick_Recall.md#L286.

Q-DBX-307: What is the #1 cost mistake?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_04_Quick_Recall.md#L289.

Q-DBX-308: Design a cost governance strategy for 200+ engineers

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_04_Quick_Recall.md#L296.

Q-DBX-309: How do you read a Spark execution plan?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_04_Quick_Recall.md#L364.

Q-DBX-310: What is a shuffle?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_04_Quick_Recall.md#L367.

Q-DBX-311: What is data skew?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_04_Quick_Recall.md#L370.

Q-DBX-312: How to fix data skew?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_04_Quick_Recall.md#L373.

Q-DBX-313: What is spill?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_04_Quick_Recall.md#L378.

Q-DBX-314: What causes OOM (Out of Memory)?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_04_Quick_Recall.md#L381.

Q-DBX-315: Pipeline is suddenly 5x slower, no code change. What do you check?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_04_Quick_Recall.md#L390.

Q-DBX-316: How to use the Spark UI to find bottlenecks?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_04_Quick_Recall.md#L400.

Q-DBX-317: What makes a good answer to system design questions?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_04_Quick_Recall.md#L458.

Q-DBX-318: What is the Medallion Architecture (Bronze/Silver/Gold)?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L187.

Q-DBX-319: What is Auto Loader in Databricks?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L190.

Q-DBX-320: What is the difference between Auto Loader and COPY INTO?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L193.

Q-DBX-321: What is Delta Live Tables (DLT)?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L196.

Q-DBX-322: What is Lakeflow and how does it relate to DLT?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L199.

Q-DBX-323: What is SCD Type 1 vs SCD Type 2?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L202.

Q-DBX-324: What is Change Data Capture (CDC)?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L205.

Q-DBX-325: What is a streaming table vs a materialized view in DLT?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L208.

Q-DBX-326: What are DLT expectations (data quality constraints)?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L211.

Q-DBX-327: What is the difference between cloudFiles and spark.readStream on Delta?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L214.

Q-DBX-328: What is structured streaming in Databricks?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L217.

Q-DBX-329: What is a checkpoint in Spark Structured Streaming?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L220.

Q-DBX-330: What is the trigger mode availableNow vs processingTime?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L223.

Q-DBX-331: What is idempotency and why is it important in ETL pipelines?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L226.

Q-DBX-332: What is the difference between batch and streaming ETL?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L229.

Q-DBX-333: What is an ETL pipeline vs an ELT pipeline?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L232.

Q-DBX-334: What are the three DLT expectation actions: warn, drop, fail?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L235.

Q-DBX-335: What is the foreachBatch sink in Structured Streaming?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L238.

Q-DBX-336: What is event-time processing vs processing-time in streaming?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L241.

Q-DBX-337: What is watermarking in Spark Structured Streaming?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L244.

Q-DBX-338: Explain how Auto Loader's file notification mode works vs directory listing mode. When do you use each?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L249.

Q-DBX-339: How do you handle schema evolution with Auto Loader (cloudFiles.schemaEvolutionMode)?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L252.

Q-DBX-340: Compare Delta Live Tables (DLT) vs hand-coded Structured Streaming pipelines β€” trade-offs?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L255.

Q-DBX-341: Explain how to implement SCD Type 2 using MERGE INTO with Delta Lake. What are the key columns?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L258.

Q-DBX-342: How does DLT handle pipeline failures and retries? What is the concept of "idempotent recomputation"?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L261.

Q-DBX-343: Compare trigger(availableNow=True) vs trigger(processingTime='5 minutes') β€” when to use each?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L264.

Q-DBX-344: Explain the role of Bronze, Silver, and Gold layers in terms of data quality, latency, and consumers.

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L267.

Q-DBX-345: How does watermarking work in Structured Streaming? What happens to late-arriving data?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L270.

Q-DBX-346: Compare CDC patterns: log-based CDC (Debezium/Kafka) vs query-based CDC vs timestamp-based CDC.

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L273.

Q-DBX-347: How do you handle exactly-once semantics in a Databricks streaming pipeline?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L276.

Q-DBX-348: Explain foreachBatch β€” when would you use it over a standard Delta sink?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L279.

Q-DBX-349: How do DLT expectations compare to Great Expectations or other data quality frameworks?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L282.

Q-DBX-350: What are the different ways to orchestrate dependent DLT pipelines?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L285.

Q-DBX-351: Explain how Auto Loader handles file deduplication. What is the RocksDB state store?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L288.

Q-DBX-352: How do you test ETL pipelines in Databricks? What frameworks do you use?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L291.

Q-DBX-353: Explain the difference between a complete output mode, append mode, and update mode in streaming.

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L294.

Q-DBX-354: How do you monitor and alert on streaming pipeline lag in Databricks?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L297.

Q-DBX-355: What is the APPLY CHANGES INTO syntax in DLT and when do you use it?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L300.

Q-DBX-356: How do you handle out-of-order events in a Medallion Architecture?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L303.

Q-DBX-357: Explain incremental data loading patterns: append-only vs upsert vs full refresh.

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L306.

Q-DBX-358: What is Unity Catalog in Databricks?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L362.

Q-DBX-359: What is the three-level namespace in Unity Catalog (catalog.schema.table)?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L365.

Q-DBX-360: What is a metastore in Unity Catalog?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L368.

Q-DBX-361: What is the difference between managed and external tables in Unity Catalog?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L371.

Q-DBX-362: What is a storage credential in Unity Catalog?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L374.

Q-DBX-363: What is an external location in Unity Catalog?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L377.

Q-DBX-364: What is data lineage in Unity Catalog?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L380.

Q-DBX-365: What is Photon engine in Databricks?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L383.

Q-DBX-366: What is Serverless compute in Databricks?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L386.

Q-DBX-367: What is ADLS Gen2 and how does Databricks connect to it?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L389.

Q-DBX-368: What is the difference between a Databricks workspace and a metastore?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L392.

Q-DBX-369: What is row-level security in Unity Catalog?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L395.

Q-DBX-370: What is column masking in Unity Catalog?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L398.

Q-DBX-371: What is a service principal in Databricks on Azure?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L401.

Q-DBX-372: What is the difference between instance profiles and storage credentials?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L404.

Q-DBX-373: What are tags and labels in Unity Catalog for data classification?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L407.

Q-DBX-374: What is the system tables feature in Unity Catalog?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L410.

Q-DBX-375: What is Databricks SQL (DBSQL)?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L413.

Q-DBX-376: What is a SQL Warehouse (Serverless vs Pro vs Classic)?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L416.

Q-DBX-377: What is GDPR and what does it mean for data engineering?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L419.

Q-DBX-378: Explain the Unity Catalog hierarchy: metastore -> catalog -> schema -> table/view/function. How do permissions cascade?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L424.

Q-DBX-379: Compare Unity Catalog vs legacy Hive Metastore β€” what are the key differences and migration challenges?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L427.

Q-DBX-380: How does Photon engine accelerate queries? What workloads benefit most from Photon?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L430.

Q-DBX-381: Compare Serverless SQL Warehouses vs Classic SQL Warehouses β€” cost, startup time, scaling.

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L433.

Q-DBX-382: Explain how ADLS Gen2 integrates with Databricks β€” authentication methods (OAuth, service principals, access keys).

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L436.

Q-DBX-383: How do you implement GDPR "Right to be Forgotten" in a Lakehouse architecture?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L439.

Q-DBX-384: Explain dynamic views in Unity Catalog for row-level and column-level security.

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L442.

Q-DBX-385: How does Unity Catalog handle cross-workspace data sharing?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L445.

Q-DBX-386: Compare Azure Databricks vs Azure Synapse Analytics β€” when would you recommend each?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L448.

Q-DBX-387: Explain how audit logging works in Unity Catalog. What events are captured?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L451.

Q-DBX-388: How do you implement data classification (PII tagging) using Unity Catalog?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L454.

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L457.

Q-DBX-390: Explain the difference between account-level and workspace-level identity in Databricks.

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L460.

Q-DBX-391: How does Unity Catalog system tables help with cost monitoring and query auditing?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L463.

Q-DBX-392: Compare managed identity vs service principal vs access key for ADLS Gen2 access β€” pros/cons.

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L466.

Q-DBX-393: Explain how Databricks handles encryption at rest and in transit on Azure.

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L469.

Q-DBX-394: How do you design a multi-region Databricks deployment on Azure?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L472.

Q-DBX-395: What is the role of Azure Key Vault in Databricks? How do you manage secrets?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L475.

Q-DBX-396: Explain the difference between GRANT, DENY, and REVOKE in Unity Catalog's permission model.

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L478.

Q-DBX-397: How does Unity Catalog's data lineage differ from tools like Apache Atlas or Purview?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L481.

Q-DBX-398: What is Databricks Workflows (formerly Jobs)?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L537.

Q-DBX-399: What is the difference between a Task and a Job in Databricks Workflows?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L540.

Q-DBX-400: What are Databricks Asset Bundles (DABs)?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L543.

Q-DBX-401: What is the Databricks CLI?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L546.

Q-DBX-402: What is a job cluster vs an all-purpose (interactive) cluster?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L549.

Q-DBX-403: What is cluster autoscaling and how does it work?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L552.

Q-DBX-404: What is a cluster policy in Databricks?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L555.

Q-DBX-405: What are spot instances and how do they reduce cost?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L558.

Q-DBX-406: What is the Databricks REST API used for?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L561.

Q-DBX-407: What is a Databricks repo (Git integration)?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L564.

Q-DBX-408: What are Databricks Notebooks vs IDE-based development?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L567.

Q-DBX-409: What is the difference between a wheel file and a notebook task in a Workflow?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L570.

Q-DBX-410: What is the Ganglia UI / Spark UI used for in debugging?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L573.

Q-DBX-411: What is a driver log vs an executor log?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L576.

Q-DBX-412: What is the Databricks DBU (Databricks Unit) and how is pricing calculated?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L579.

Q-DBX-413: What are init scripts and when would you use them?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L582.

Q-DBX-414: What is a multi-task workflow (DAG) in Databricks?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L585.

Q-DBX-415: What is the dbutils library and what are its key modules?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L588.

Q-DBX-416: What are widgets in Databricks notebooks?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L591.

Q-DBX-417: What is the difference between %run and dbutils.notebook.run()?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L594.

Q-DBX-418: Explain Databricks Asset Bundles (DABs) β€” how do they enable CI/CD for Databricks projects?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L599.

Q-DBX-419: Compare DABs vs Terraform for Databricks infrastructure management β€” when to use each?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L602.

Q-DBX-420: How do you implement a CI/CD pipeline for Databricks using Azure DevOps?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L605.

Q-DBX-421: Explain how Databricks Workflows handles task dependencies, retries, and conditional execution.

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L608.

Q-DBX-422: Compare job clusters vs all-purpose clusters β€” cost, startup time, use cases.

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L611.

Q-DBX-423: How do you debug an OOM (Out of Memory) error in a Databricks Spark job?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L614.

Q-DBX-424: Explain how to read and interpret the Spark UI: stages, tasks, shuffle read/write, spill.

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L617.

Q-DBX-425: How do you implement blue-green or canary deployments for Databricks ETL pipelines?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L620.

Q-DBX-426: Explain cluster pool strategy β€” how do pools reduce cluster startup time and cost?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L623.

Q-DBX-427: How do you manage secrets and environment-specific configurations across dev/staging/prod?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L626.

Q-DBX-428: Compare Databricks Repos (Git integration) vs external CI/CD tools for version control.

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L629.

Q-DBX-429: How do you implement data pipeline monitoring and alerting in Databricks?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L632.

Q-DBX-430: Explain the cost implications of spot instances vs on-demand for different workload types.

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L635.

Q-DBX-431: How do you diagnose data skew in a Spark job using the Spark UI?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L638.

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L641.

Q-DBX-433: How do you implement parameterized jobs with dynamic values in Workflows?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L644.

Q-DBX-434: Explain the difference between task values (dbutils.jobs.taskValues) and widget parameters.

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L647.

Q-DBX-435: How do you handle failing tasks in a DAG β€” retry policies, timeout, conditional logic?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L650.

Q-DBX-436: Compare Serverless jobs vs provisioned clusters for job execution β€” cost breakeven analysis.

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L653.

Q-DBX-437: How do you implement logging and observability for production Databricks pipelines?

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_05_QUESTION_BANK_ALL_LEVELS.md#L656.

Q-DBX-438: Confusion 1: All-Purpose vs Job vs SQL Warehouse Cluster

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_07_Confusions_Labs_MockInterview.md#L28.

Q-DBX-439: Confusion 2: DBFS vs Workspace Files vs Volumes

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_07_Confusions_Labs_MockInterview.md#L50.

Q-DBX-440: Confusion 3: Jobs vs Delta Live Tables (DLT)

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_07_Confusions_Labs_MockInterview.md#L71.

Q-DBX-441: Confusion 4: Photon vs Standard Spark

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_07_Confusions_Labs_MockInterview.md#L102.

Q-DBX-442: Confusion 5: Auto Loader vs COPY INTO vs readStream

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_07_Confusions_Labs_MockInterview.md#L125.

Q-DBX-443: Confusion 6: Managed vs External Tables (Unity Catalog)

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_07_Confusions_Labs_MockInterview.md#L152.

Q-DBX-444: Confusion 7: Unity Catalog Namespace Levels

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_07_Confusions_Labs_MockInterview.md#L183.

Q-DBX-445: Confusion 8: Databricks Workflows vs Azure Data Factory / Airflow

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_07_Confusions_Labs_MockInterview.md#L204.

Q-DBX-446: Gotcha 1: DBFS mount token expiration

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_07_Confusions_Labs_MockInterview.md#L525.

Q-DBX-447: Gotcha 2: "Table is not a Delta table" after converting from Parquet

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_07_Confusions_Labs_MockInterview.md#L536.

Q-DBX-448: Gotcha 3: dbutils not available in spark-submit / local dev

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_07_Confusions_Labs_MockInterview.md#L552.

Q-DBX-449: Gotcha 4: Job failing silently β€” no error in UI

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_07_Confusions_Labs_MockInterview.md#L568.

Q-DBX-450: Gotcha 5: display() cuts off rows beyond 1000

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_07_Confusions_Labs_MockInterview.md#L575.

Q-DBX-451: Gotcha 6: Photon disabled warnings in logs

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_07_Confusions_Labs_MockInterview.md#L589.

Q-DBX-452: MOCK INTERVIEW

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_07_Confusions_Labs_MockInterview.md#L600.

Q-DBX-453: "Design a production data platform for 200+ engineers on Databricks."

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_07_Confusions_Labs_MockInterview.md#L602.

Q-DBX-454: "Our daily ETL takes 4 hours and is getting slower. Debug it."

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_07_Confusions_Labs_MockInterview.md#L609.

Q-DBX-455: "GDPR asks us to delete a passenger's data from every Delta table. How?"

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_07_Confusions_Labs_MockInterview.md#L616.

Q-DBX-456: "When would you NOT use Delta Live Tables?"

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_07_Confusions_Labs_MockInterview.md#L623.

Q-DBX-457: "How do you reduce Databricks costs by 30%?"

Answer owner: Open the canonical concept or runnable pattern.

Alternate source wording: content/databricks/DB_07_Confusions_Labs_MockInterview.md#L630.

90 seconds

Practice sprint

Close the atlas. Rebuild the map.

Name the path from API to files, then explain where shuffle, skew, and serialization enter the system.

Open interview prompts