🔍 Ctrl+K
🟡 Intermediate

Interview Preparation

Master PySpark interviews with these carefully curated questions organized by difficulty level. Each question includes a brief answer to help you prepare.

Next: Beginner Questions →
🟢 Easy

Beginner Questions

What is Spark?

Apache Spark is a unified analytics engine for large-scale data processing. It provides high-level APIs in Java, Scala, Python, and R. Spark supports SQL queries, streaming data, machine learning, and graph processing. It processes data in memory, making it significantly faster than traditional MapReduce.

What is PySpark?

PySpark is the Python API for Apache Spark. It allows Python developers to write Spark applications and leverage Spark's distributed computing capabilities. PySpark provides the SparkContext and SparkSession classes for interacting with Spark.

What is an RDD?

RDD (Resilient Distributed Dataset) is the fundamental data structure of Spark. It is an immutable, distributed collection of objects that can be processed in parallel. RDDs support two types of operations: transformations (create new RDDs) and actions (return values to the driver).

What is a partition?

A partition is a logical chunk of data in Spark. Spark divides datasets into partitions to enable parallel processing. Each partition is processed by a single task on a single executor core. The number of partitions determines the level of parallelism.

What is a transformation?

A transformation is an operation that creates a new dataset from an existing one. Transformations are lazily evaluated, meaning they are not executed immediately. Examples include map(), filter(), and join(). Transformations build up a lineage graph.

What is an action?

An action is an operation that triggers Spark to execute the computation and returns a result to the driver or writes data to storage. Examples include collect(), count(), show(), and saveAsTextFile(). Actions trigger the execution of the DAG.

What is lazy evaluation?

Lazy evaluation means that transformations are not executed immediately when they are called. Instead, Spark records the transformations and builds a lineage graph. Execution only happens when an action is called. This allows Spark to optimize the entire execution plan.

🟡 Intermediate

Intermediate Questions

What is the difference between narrow and wide transformations?

Narrow transformations (map, filter) process each input partition to produce one output partition - no data movement. Wide transformations (groupBy, join, reduceByKey) require data to be shuffled across partitions, creating new stages. Narrow is faster; wide is more expensive.

What is a shuffle?

A shuffle is the process of redistributing data across partitions. It occurs during wide transformations when data needs to be moved between executors. Shuffles are expensive because they involve disk I/O, data serialization, and network I/O. They create new stage boundaries.

What is a DAG?

DAG (Directed Acyclic Graph) is the logical execution plan that Spark creates for a job. It represents the sequence of transformations and actions as a graph of operations. Spark optimizes the DAG before execution and divides it into stages at shuffle boundaries.

What is a stage?

A stage is a set of tasks that can be executed without data shuffle. Spark divides the DAG into stages at shuffle boundaries. Each stage contains tasks that process partitions in parallel. Stages are connected by shuffles.

What is a task?

A task is a unit of work that processes a single partition. It runs on a single executor core. The number of tasks in a stage equals the number of partitions. Tasks are the smallest unit of execution in Spark.

How does Spark execute a job?

Spark execution flow: User code → Driver creates SparkSession → Logical plan → Catalyst optimization → DAG → Stages (split at shuffles) → Tasks → Cluster manager assigns tasks → Executors run tasks → Results returned to driver.

What is hash partitioning?

Hash partitioning uses a hash function on the partition key to determine which partition a record goes to. Formula: partition = hash(key) % number_of_partitions. Records with the same key always go to the same partition, which is useful for joins and aggregations.

What is range partitioning?

Range partitioning divides data based on value ranges. Each partition covers a specific range (e.g., 0-100, 101-200). Useful for maintaining sorted order and efficient range queries. Spark can automatically determine ranges based on data distribution.

What is the difference between cache and persist?

Cache stores data in memory (MEMORY_ONLY) for reuse. Persist allows choosing different storage levels (MEMORY_ONLY, MEMORY_AND_DISK, etc.). Both keep data for reuse across actions. Use cache for simple cases; persist when you need specific storage levels.

What is a broadcast join?

Broadcast join sends the smaller DataFrame to all executors, avoiding shuffle of the larger DataFrame. Useful when one side is small enough to fit in memory. Triggered automatically when the smaller side is below the broadcast threshold (default 10MB). Fastest join strategy.

🔴 Advanced

Advanced Questions

What is data skewness?

Data skew occurs when data is unevenly distributed across partitions. Some partitions have significantly more data than others, causing those tasks to take much longer. This leads to OOM errors, slow jobs, and underutilized resources.

How do you solve data skew?

Solutions: (1) Salting - add random prefix to skewed keys, (2) Repartition to redistribute data, (3) Broadcast join for small-large joins, (4) Use AQE skew join optimization, (5) Isolate and process skewed keys separately.

Explain salting.

Salting adds a random prefix to skewed keys to distribute data more evenly. Steps: (1) Add random salt to skewed key, (2) Aggregate with salted key, (3) Remove salt and aggregate again. This distributes the load across multiple partitions.

Explain sort-merge join.

Sort-merge join is used for large-large joins. Both DataFrames are hash-partitioned on the join key, each partition is sorted, then sorted partitions are merged. Default strategy for large datasets. Requires shuffle and sort.

When would you use broadcast join?

Use broadcast join when one DataFrame is small enough to fit in executor memory (typically < 10MB). It avoids expensive shuffle of the larger DataFrame. Common for large transaction data with small dimension data.

How do you troubleshoot a slow Spark job?

(1) Check Spark UI for long stages, (2) Look for large shuffles, (3) Check data skew, (4) Verify partition count, (5) Check join strategies, (6) Review caching, (7) Check for unnecessary operations, (8) Optimize bottlenecks.

How do you troubleshoot executor OOM?

(1) Check Spark UI for task metrics, (2) Identify large partitions, (3) Increase executor memory, (4) Repartition to reduce partition size, (5) Use persist with MEMORY_AND_DISK, (6) Avoid collect() on large data, (7) Use broadcast joins.

How can you avoid driver failure?

(1) Avoid collect() on large datasets, (2) Use take() or show() instead, (3) Increase driver memory, (4) Use broadcast variables for shared data, (5) Don't accumulate large results in driver, (6) Monitor driver metrics.

What is Catalyst Optimizer?

Catalyst is Spark's query optimization framework. It optimizes logical plans through rule-based and cost-based optimization. Key optimizations: predicate pushdown, column pruning, constant folding, join reordering.

What is AQE?

Adaptive Query Execution optimizes queries at runtime based on actual data statistics. Features: coalescing shuffle partitions, converting sort-merge to broadcast joins, optimizing skew joins. Enabled by default in Spark 3.0+.

How would you optimize a Spark job?

(1) Use broadcast joins, (2) Cache reused data, (3) Reduce shuffles, (4) Handle data skew, (5) Tune partitions, (6) Use efficient file formats, (7) Filter early, (8) Use AQE, (9) Monitor Spark UI, (10) Profile and optimize bottlenecks.

How would you handle the small-file problem?

(1) Compact files using coalesce/repartition, (2) Use Parquet/ORC format, (3) Configure maxRecordsPerFile, (4) Use Hadoop input format for merging, (5) Schedule compaction jobs.

🔴 Advanced

Real-World Spark Interview Simulator

Practice these real-world scenarios to prepare for system design and troubleshooting interviews.

Scenario: Your Spark job is taking 40 minutes instead of 5 minutes.

Step 1 Check Spark UI

Open the Spark UI and navigate to the running or completed job. Look at the DAG visualization and stage details to understand where time is being spent.

Step 2 Look for long-running stages

Identify stages that take significantly longer than others. These are the bottleneck stages that need investigation.

Step 3 Check shuffle size

Large shuffle read/write indicates expensive data movement. Check if the shuffle partitions count is appropriate (default 200 may be too high or too low).

Step 4 Check data skew

Look at task metrics - if some tasks take much longer than others, data skew is likely the cause. Check for keys with disproportionately large amounts of data.

Step 5 Check partition count

Too few partitions lead to underutilization; too many lead to overhead. Optimal partition size is 128MB-256MB per partition.

Step 6 Check joins

Verify if broadcast joins are being used where possible. Large-large joins using sort-merge may be slower than broadcast joins for small-large combinations.

Step 7 Check caching/persistence

Determine if data is being recomputed multiple times. Cache or persist data that is reused across multiple actions.

Step 8 Optimize the bottleneck

Apply the appropriate fix: repartition data, use broadcast joins, cache frequently accessed data, filter early, or tune configuration parameters.

Scenario: One task in your Spark job takes 10x longer than others.

Step 1 Identify the skewed key

Check the Spark UI task metrics. The slow task processes a partition with an disproportionately large amount of data, likely due to a single key with many records.

Step 2 Analyze the data distribution

Run a groupBy on the suspect key and count records per key. Identify if a few keys dominate the dataset.

Step 3 Apply salting technique

Add a random prefix to the skewed key to distribute it across multiple partitions. This spreads the load evenly.

Step 4 Aggregate with salted key

Perform the aggregation using the salted key instead of the original key. This ensures parallel processing across multiple partitions.

Step 5 Remove salt and re-aggregate

Strip the salt prefix from the keys and perform a final aggregation to get the correct results.

Step 6 Alternative: Use AQE skew join

Enable Adaptive Query Execution which can automatically detect and optimize skew joins in Spark 3.0+.

Scenario: Your Spark executor throws OutOfMemoryError.

Step 1 Check Spark UI for task metrics

Look at the failed tasks in the Spark UI. Identify which task failed and what data it was processing.

Step 2 Identify large partitions

Check if certain partitions are significantly larger than others. This is often the root cause of OOM errors.

Step 3 Increase executor memory

Adjust spark.executor.memory and spark.executor.memoryOverhead to allocate more memory per executor.

Step 4 Repartition to reduce partition size

Use repartition() to split large partitions into smaller ones. Aim for 128MB-256MB per partition.

Step 5 Use persist with MEMORY_AND_DISK

Instead of MEMORY_ONLY, use MEMORY_AND_DISK to spill data to disk when memory is insufficient.

Step 6 Avoid collect() on large data

collect() brings all data to the driver which can cause driver OOM. Use take(), show(), or write to storage instead.

Step 7 Use broadcast joins

Broadcast join avoids shuffling large DataFrames and reduces memory pressure on executors.

Scenario: Your Spark driver crashes when calling collect().

Step 1 Identify the collect() call

Locate where collect() is being used in your code. This is typically the root cause of driver OOM.

Step 2 Replace with take() or show()

Use take(n) to get only a subset of records, or show() to display a preview. This avoids bringing all data to the driver.

Step 3 Increase driver memory

Adjust spark.driver.memory to allocate more memory to the driver process.

Step 4 Use broadcast variables

For shared lookup data, use broadcast variables instead of collecting data to the driver.

Step 5 Don't accumulate large results

Avoid accumulating results in driver-side variables. Write results directly to storage using write operations.

Step 6 Monitor driver metrics

Check driver metrics in Spark UI to track memory usage and identify when memory pressure builds up.

Scenario: Your join operation is taking too long.

Step 1 Check join strategy in Spark UI

Look at the query plan in the Spark UI to see which join strategy is being used (broadcast, sort-merge, or shuffle hash).

Step 2 Determine if broadcast is possible

If one side of the join is small enough (typically < 10MB), use broadcast join to avoid expensive shuffles.

Step 3 Check data skew in join keys

Skewed join keys cause uneven partition sizes. Some tasks will take much longer, slowing the overall join.

Step 4 Pre-partition by join key

Repartition both DataFrames by the join key before joining to reduce shuffle overhead.

Step 5 Filter data before joining

Apply filters as early as possible to reduce the amount of data being joined.

Step 6 Use broadcast hint

Force broadcast join using .hint("broadcast") or the broadcast() function if you know one side is small.

Step 7 Enable AQE

Adaptive Query Execution can automatically convert sort-merge to broadcast joins based on runtime statistics.

Scenario: Your Spark job processes thousands of small files.

Step 1 Identify the small files problem

Many small files cause excessive task overhead, slow job startup, and underutilized resources. Each file creates a separate task.

Step 2 Compact files using coalesce/repartition

Use coalesce() to reduce the number of partitions and combine small files into larger ones.

Step 3 Use Parquet/ORC format

Columnar formats like Parquet and ORC handle small files better and support predicate pushdown for faster queries.

Step 4 Configure maxRecordsPerFile

Set spark.sql.files.maxRecordsPerFile to limit the number of records per output file during writes.

Step 5 Use Hadoop input format for merging

Use combineInputFormat or similar to merge small files at the input level before processing.

Step 6 Schedule compaction jobs

Set up periodic compaction jobs to merge small files into larger, optimized files.