SPARK Reference Guide
Revision Time: 15 mins

PySpark Interview Questions & Answers

Curated Top 20 most frequently asked PySpark Data Engineering interview questions, architecture scenarios, code snippets, and Databricks real-world solutions.

Q1: What is the difference between RDD, DataFrame, and DataSet?
Return: Core Concept

Core fundamental question testing knowledge of Spark abstraction evolution.

Used In: PySpark Architecture Fundamentals

Syntax signature:RDD (Low-level) vs DataFrame (Row Struct) vs DataSet (Type-safe Scala/Java)
Code snippet:
python
# RDD: No schema, opaque Java objects
rdd = sc.parallelize([('Alice', 30)])

# DataFrame: Named columns, Catalyst optimized
df = spark.createDataFrame([('Alice', 30)], ['name', 'age'])
Expected Output:RDD: Unstructured | DataFrame: Structured Schema | DataSet: Type-safe
Common Mistakes:Saying DataSets exist in Python. Python PySpark ONLY supports DataFrames and RDDs.
Remember: Key Distinction: RDDs lack Catalyst optimization. DataFrames use Catalyst optimizer for 10x-100x faster execution. DataSets offer compile-time type safety in Scala/Java but are unavailable in Python due to Python's dynamic typing.
Q2: Explain Lazy Evaluation in PySpark and why it is beneficial.
Return: Core Concept

Tests understanding of Spark execution model (Transformations vs Actions).

Used In: Query Optimization, DAG Build

Syntax signature:Transformations (Lazy) -> Action (Triggers DAG Execution)
Code snippet:
python
df1 = spark.read.csv('data.csv') # Lazy (No execution)
df2 = df1.filter(col('age') > 21) # Lazy (No execution)
df3 = df2.select('name') # Lazy (No execution)
df3.count() # ACTION: Triggers DAG build & physical plan execution!
Expected Output:Count: 4500
Common Mistakes:Thinking `df.filter()` or `df.select()` reads data from disk immediately.
Remember: Why it matters: Lazy evaluation enables Catalyst Optimizer to inspect the entire lineage graph, apply Predicate Pushdown (filter before read), Column Pruning (load required columns only), and combine operations into a single stage.
Q3: What is the difference between Narrow and Wide Transformations?
Return: Core Concept

Tests understanding of network shuffles and stage boundaries.

Used In: Stage Boundary Analysis, Bottleneck Debugging

Syntax signature:Narrow: 1-to-1 partition mapping | Wide: Many-to-Many shuffle mapping
Code snippet:
python
# Narrow (No Shuffle): filter, map, select, flatMap, union
df_narrow = df.filter(col('status') == 'ACTIVE')

# Wide (Triggers Network Shuffle): groupBy, join, distinct, repartition, reduceByKey
df_wide = df.groupBy('department').count()
Expected Output:Narrow: 0 Shuffles (Fast) | Wide: Network Shuffle Stage Boundary
Remember: Narrow transformations execute within the same executor partition without network transfers. Wide transformations require moving data across nodes (Shuffle), breaking the job into multiple Stages.
Q4: Explain how Spark Shuffle works and why it is expensive.
Return: Architecture

Tests deep understanding of distributed data movement across cluster nodes.

Used In: Shuffle Management, Performance Optimization

Syntax signature:Map Side Write -> Disk Spill -> Network Transfer -> Reduce Side Read
Code snippet:
python
# Shuffle triggered by Wide transformation:
df.groupBy('country').sum('revenue')
Expected Output:Data re-distributed across worker network partitions
Common Mistakes:Assuming shuffles happen purely in memory. Heavy shuffles spill to executor disk.
Remember: Why Shuffle is expensive: (1) Serializes data to bytes, (2) Writes shuffle files to local disk, (3) Transfers data over network sockets, (4) Reads and deserializes bytes on reducer nodes. Minimizing shuffles is #1 performance rule.
Q5: What is the difference between repartition() and coalesce()?
Return: Partitioning

Extremely frequent interview question on partition management.

Used In: Output File Consolidation, Partition Resizing

Syntax signature:repartition(N) [Full Shuffle] vs coalesce(N) [No Shuffle for Reducing]
Code snippet:
python
# Reduce 100 partitions to 10 efficiently without shuffle:
df_coalesced = df.coalesce(10)

# Increase 10 partitions to 50 or rebalance skewed data (Full Shuffle):
df_repartitioned = df.repartition(50)
Expected Output:coalesce: Zero Shuffle | repartition: Full Network Shuffle
Common Mistakes:Calling `coalesce(100)` when initial partitions are 10. `coalesce` without `shuffle=True` CANNOT increase partition count.
Remember: Use `coalesce` to REDUCE partition count (combines adjacent partitions on same node). Use `repartition` to INCREASE partition count or rebalance heavily skewed data.
Q6: Why is reduceByKey preferred over groupByKey in RDDs?
Return: Optimization

Tests knowledge of map-side combining (combiners).

Used In: RDD Aggregation Optimization

Syntax signature:reduceByKey (Map-side Combine) vs groupByKey (Full Network Transfer)
Code snippet:
python
rdd = sc.parallelize([('a', 1), ('a', 2), ('b', 3)])
# Efficient: Combines locally on mapper node before sending across network
rdd.reduceByKey(lambda x, y: x + y)
Expected Output:[('a', 3), ('b', 3)]
Common Mistakes:Using `groupByKey().mapValues(sum)` instead of `reduceByKey(add)`.
Remember: `reduceByKey` performs map-side aggregation (combiner) locally on each mapper node BEFORE shuffling bytes across the network. `groupByKey` sends ALL raw key-value pairs over the network, causing severe OOM errors.
Q7: What is Broadcast Join and when should you use it?
Return: Join Strategy

Tests join optimization techniques for small lookup tables.

Used In: Fact-Dimension Joins, Lookup Tables

Syntax signature:from pyspark.sql.functions import broadcast large_df.join(broadcast(small_df), 'key')
Code snippet:
python
orders_df.join(broadcast(country_lookup_df), 'country_code')
Expected Output:BroadcastHashJoin executed without shuffling large_df
Common Mistakes:Broadcasting large tables (>1GB), causing Driver OOM during broadcast collection.
Remember: When 1 table is small (<10MB default, configurable up to ~100MB), Spark copies the small table to ALL executor nodes. The large table stays in place without any network shuffle (BroadcastHashJoin).
Q8: What is Data Skew and how do you resolve it?
Return: Optimization Scenario

Scenario question asked in 90%+ Senior Data Engineer interviews.

Used In: Straggler Task Mitigation, High-volume ETL

Syntax signature:Salting: Add random salt key to spread skewed key across partitions
Code snippet:
python
# Add random salt (0-9) to skewed dataframe:
df_salted = df.withColumn('salted_key', concat(col('user_id'), lit('_'), floor(rand() * 10)))
Expected Output:Uniform partition distribution without straggler tasks
Remember: Symptom: 99 executor tasks complete in 10 seconds, but 1 task takes 2 hours (or crashes OOM). Solution: (1) Enable AQE Skew Join (`spark.sql.adaptive.skewJoin.enabled=true`), or (2) Apply Salting (append random integer suffix to key).
Q9: How do you handle OutOfMemory (OOM) errors in Spark?
Return: Troubleshooting

Debugging and production troubleshooting scenario.

Used In: Production Incident Debugging, Cluster Sizing

Syntax signature:Driver OOM vs Executor OOM
Code snippet:
python
# Driver OOM Fix: Avoid collect(), increase driver RAM:
--driver-memory 8g

# Executor OOM Fix: Increase executor RAM, fix data skew, lower shuffle partitions:
--executor-memory 16g --conf spark.memory.fraction=0.8
Expected Output:Stable memory allocation without container eviction
Remember: Driver OOM causes: `collect()`, `broadcast()` on large tables, huge driver schema. Executor OOM causes: Data skew, heavy joins/sorts, small partition count causing disk spill, building huge lists in UDFs.
Q10: What is Adaptive Query Execution (AQE) in Spark 3.x?
Return: Spark 3 Feature

Tests modern Spark 3.x runtime optimization features.

Used In: Production Spark 3.x Optimization

Syntax signature:spark.conf.set('spark.sql.adaptive.enabled', 'true')
Code snippet:
python
# AQE automatically applies 3 key runtime optimizations:
# 1. Coalesces small shuffle partitions dynamically
# 2. Converts Sort-Merge Join to Broadcast Join at runtime
# 3. Handles Skew Joins automatically
Expected Output:Dynamic physical plan adaptation at stage boundaries
Remember: AQE re-evaluates physical plan statistics AFTER each shuffle stage completes, adjusting downstream partition counts dynamically.
Q11: Explain PySpark UDFs vs Pandas UDFs (Vectorized UDFs).
Return: PySpark Performance

Tests Python-to-JVM serialization knowledge and Apache Arrow.

Used In: Custom Python Logic, Vectorized Data Processing

Syntax signature:@pandas_udf(DoubleType()) def calc(v: pd.Series) -> pd.Series:
Code snippet:
python
import pandas as pd
from pyspark.sql.functions import pandas_udf

@pandas_udf('double')
def vectorized_tax(salary: pd.Series) -> pd.Series:
    return salary * 0.2

df.withColumn('tax', vectorized_tax(col('salary')))
Expected Output:10x-100x faster execution using Apache Arrow zero-copy memory transfer
Common Mistakes:Using standard `@udf` for heavy numeric operations instead of PyArrow Vectorized `@pandas_udf`.
Remember: Standard PySpark UDFs serialize data row-by-row between Python and JVM (very slow). Pandas UDFs use Apache Arrow to transfer batch vectors in memory (10x-100x faster).
Q12: How do you fix the 'Small File Problem' in Spark/Delta Lake?
Return: Storage Optimization

Data Lake storage architecture question.

Used In: Lakehouse Maintenance, Parquet Storage Optimization

Syntax signature:coalesce(N) | Delta OPTIMIZE / ZORDER
Code snippet:
python
# For vanilla Parquet: Coalesce before write
df.coalesce(5).write.parquet('/path')

# For Delta Lake: Run OPTIMIZE and ZORDER
spark.sql('OPTIMIZE delta_table ZORDER BY (date, customer_id)')
Expected Output:Compacted 100MB-1GB target files
Remember: Small File Problem: Generating millions of 2KB files degrades metastore directory listing and read speed. Fix by coalescing before write or running Delta `OPTIMIZE` to compact small files into 1GB blocks.
Q13: How does the Catalyst Optimizer work?
Return: Engine Architecture

Deep engine internals question.

Used In: Catalyst Engine Internals

Syntax signature:Unresolved Logical Plan -> Analyzed -> Optimized -> Physical Plan -> Code Generation
Code snippet:
python
# 4 Catalyst Stages:
# 1. Analysis (Catalog verification of column names/types)
# 2. Logical Optimization (Predicate pushdown, column pruning)
# 3. Physical Planning (Selecting join algorithms: Broadcast vs Sort-Merge)
# 4. Code Generation (WholeStageCodegen compiling Java bytecode)
Expected Output:Optimized Java Bytecode Execution Plan
Remember: Catalyst uses Scala pattern matching to rewrite logical trees automatically (e.g. pushing `filter()` before `join()`).
Q14: What is the difference between cache() and persist()?
Return: Memory Control

Memory storage levels question.

Used In: Multi-action Pipeline Caching

Syntax signature:cache() [MEMORY_AND_DISK] vs persist(StorageLevel) [Custom Level]
Code snippet:
python
from pyspark import StorageLevel

# cache() uses default MEMORY_AND_DISK in DataFrames:
df.cache()

# persist() allows custom storage levels:
df.persist(StorageLevel.MEMORY_ONLY_SER)
Expected Output:DataFrame cached in executor RAM/Disk
Remember: `cache()` is a shortcut for `persist(StorageLevel.MEMORY_AND_DISK)` in DataFrames. `persist()` lets you choose specific storage levels (`MEMORY_ONLY`, `DISK_ONLY`, `OFF_HEAP`). Always `unpersist()` when finished.
Q15: What are Accumulators and Broadcast Variables?
Return: Shared Variables

Shared variable primitives in Spark.

Used In: Global Counters, Read-only Dictionary Lookups

Syntax signature:Accumulators (Write-only counters) | Broadcast Variables (Read-only lookups)
Code snippet:
python
# Broadcast Variable (Read-only on workers):
b_lookup = sc.broadcast({'US': 'United States', 'CA': 'Canada'})

# Accumulator (Write-only aggregate from workers):
err_counter = sc.accumulator(0)
Expected Output:Shared cluster variables
Remember: Broadcast variables copy read-only lookup data to all workers once. Accumulators collect write-only metrics (like corrupt row counts) back to the driver.
Q16: How do Window Functions work and what is the difference between row_number, rank, and dense_rank?
Return: Window Functions

SQL/PySpark ranking and analytical query question.

Used In: Top-N per Category, Leaderboards, Running Aggregations

Syntax signature:row_number() vs rank() vs dense_rank() OVER (PARTITION BY ... ORDER BY ...)
Code snippet:
python
from pyspark.sql.window import Window
from pyspark.sql.functions import row_number, rank, dense_rank

w = Window.partitionBy('dept').orderBy(col('salary').desc())
df.select('salary',
    row_number().over(w).alias('rn'), # 1, 2, 3
    rank().over(w).alias('rk'),       # 1, 1, 3 (skips 2 on tie)
    dense_rank().over(w).alias('dr')  # 1, 1, 2 (no gap on tie)
)
Expected Output:Tied Salaries [100, 100, 90] -> rn:[1,2,3], rk:[1,1,3], dr:[1,1,2]
Remember: Use `row_number()` for strict deduplication (Top-1 per group). Use `dense_rank()` when tied ranks should share position without skipping consecutive rank numbers.
Q17: What is Delta Lake and how does it differ from raw Parquet files?
Return: Lakehouse Storage

Modern Databricks Lakehouse storage architecture question.

Used In: Lakehouse Data Architecture, Bronze/Silver/Gold Medallion

Syntax signature:Delta = Parquet Data Files + JSON Transaction Log (_delta_log/)
Code snippet:
python
# Delta Lake Features:
# 1. ACID Transactions (Serializability)
# 2. Time Travel (Query past table versions)
# 3. MERGE INTO (Upserts & Deletes)
# 4. Schema Enforcement & Schema Evolution
Expected Output:ACID compliant Lakehouse table format
Remember: Raw Parquet has no transaction log; concurrent writes cause corrupt files. Delta Lake adds a `_delta_log/` transaction log for ACID guarantees, Time Travel, and `MERGE INTO` operations.
Q18: Explain Dynamic Partition Pruning (DPP) in Spark.
Return: Query Optimization

Advanced query optimization feature.

Used In: Star Schema Data Warehouse Queries

Syntax signature:spark.sql.optimizer.dynamicPartitionPruning.enabled = true
Code snippet:
python
# Joining Fact table partitioned by date_id with filtered Date Dim table:
fact_sales.join(dim_date.filter(col('year') == 2024), 'date_id')
Expected Output:Skips reading non-matching partition directories at runtime
Remember: Spark dynamically passes the filtered key values from the dimension table to the file reader of the fact table, pruning non-matching partition folders before reading files.
Q19: What is Watermarking in Structured Streaming?
Return: Structured Streaming

Real-time streaming window analytics question.

Used In: Real-time Event Streaming, Late Data Handling

Syntax signature:df.withWatermark('event_time', '10 minutes')
Code snippet:
python
df.withWatermark('event_time', '10 minutes')\
  .groupBy(window('event_time', '5 minutes'), 'device_id')\
  .count()
Expected Output:Late arriving event handling window
Remember: Watermarking defines how late data can arrive before being dropped. A 10-minute watermark tells Spark to keep state for 10 minutes past event time, dropping events older than 10 minutes.
Q20: What is the difference between Client and Cluster deploy modes?
Return: Deployment Mode

Cluster submit and deployment mode question.

Used In: Production Job Scheduling, Databricks Jobs

Syntax signature:spark-submit --deploy-mode client | cluster
Code snippet:
python
# Client Mode: Driver runs on submit machine (Notebooks/IDE)
spark-submit --deploy-mode client script.py

# Cluster Mode: Driver runs inside worker node in cluster (Production)
spark-submit --deploy-mode cluster script.py
Expected Output:Client: Local Driver | Cluster: Distributed Worker Driver
Remember: Client mode runs the Driver process on the machine where you launched `spark-submit` (ideal for interactive notebooks/debugging). Cluster mode spawns the Driver inside one of the worker nodes in the cluster (ideal for production scheduled jobs).