Apache Spark Interview Questions and Answers (2026)
Sixteen Apache Spark questions from real Indian data engineering loops — engine internals, Catalyst and Tungsten, skew and shuffle tuning, and the executor-sizing math that senior rounds demand.
By Durgesh Yadav — Senior Data Engineer @ 7-Eleven · Updated 19 Oct 2026. Preparation guidance, not a hiring guarantee.
What are the most common Apache Spark interview questions?
Spark interviews test RDD vs DataFrame vs Dataset, lazy evaluation, wide vs narrow transformations, and shuffle mechanics, then go deeper into Catalyst and Tungsten, broadcast joins, data-skew fixes like salting and AQE skew handling, persistence levels, dynamic allocation, and executor sizing. Senior rounds expect you to reason about a slow job from the Spark UI, not recite definitions.
Which engine fundamentals does every round open with?
Interviews start with the abstractions and the execution model: RDD vs DataFrame vs Dataset, lazy evaluation, and why stages break where they do. Getting these crisp matters because every tuning question later is built on shuffle boundaries and lineage.
RDD vs DataFrame vs Dataset — and why DataFrame is just Dataset[Row]
Transformations vs actions, lazy evaluation, and lineage recomputation
Narrow vs wide dependencies and stage boundaries at shuffles
What is the difference between local, client and cluster deploy modes in Spark?
The question is really where the driver lives, because that decides what dies when a laptop closes and where collect() results land. We use client mode from notebooks and gateway nodes, and cluster mode for anything a scheduler submits, so the job outlives the submitter. Local mode is for tests.
Put the driver where the job can survive
Dimension
local
client
cluster
Where the driver runs
In one JVM with the executors, on one machine
On the machine that ran spark-submit
On a node inside the cluster
Executors
Threads inside that same JVM
On cluster nodes
On cluster nodes
Submitting machine disconnects
Same process, so the job ends
Job dies with the driver
Job keeps running
Driver logs
On your terminal
On your terminal
On the cluster; fetch them from the cluster manager
Interactive shells
Yes
Yes; spark-shell and pyspark use it
No
Right answer for
Unit tests and laptop development
Notebooks, debugging, a gateway node near the cluster
Scheduled production jobs from Airflow
Why is performance tuning where offers are decided?
Mid and senior rounds move quickly to broken-job scenarios: a join stuck on its last task, a broadcast that OOMs the driver, a cache that made things slower. Interviewers grade whether you diagnose from evidence — task durations and shuffle read sizes in the Spark UI — before naming a fix.
Skew diagnosis and the fix ladder: null handling, AQE, broadcast, salting
Broadcast hash joins, the 10MB autoBroadcastJoinThreshold, and hints
At 4+ years, expect whiteboard math: size executors for a given node spec, explain the unified memory model, and justify dynamic allocation settings for a shared cluster. GCCs and product companies in India use these to separate engineers who ran production Spark from those who ran notebooks.
Executor sizing: ~5 cores per executor, memoryOverhead, OS headroom
Unified memory: execution vs storage, soft boundary, eviction rules
Dynamic allocation and why shuffle files constrain executor release
How do you fix a skewed join, and what does the plan tell you?
The interviewer's favourite scenario is a join that runs for an hour because one key holds a third of the rows. The answer has three layers, and the plan proves each. First, read the plan: df.explain() showing SortMergeJoin means both sides were shuffled on the key; BroadcastHashJoin means the small side was shipped to every executor and the big side was never partitioned by the hot key. Second, if one side is small (under spark.sql.autoBroadcastJoinThreshold, 10 MB by default, or forced with broadcast()), broadcast it and the skew disappears with the shuffle. Third, if both sides are large, let adaptive query execution split the oversized partition (spark.sql.adaptive.enabled and spark.sql.adaptive.skewJoin.enabled, both on by default since Spark 3.2) or salt the key by hand: append a random suffix from 0 to N to the hot key on the big side, replicate the small side N times, join on the salted key.
Read the plan before touching config: SortMergeJoin plus one long-running task is skew, not slowness
Broadcast when one side is small; force it with broadcast(df) when table statistics are missing
AQE skew join splits the oversized partition on its own; salting is the manual version for older clusters
repartition(n) shuffles everything; coalesce(n) only merges partitions, so use it to shrink, not to rebalance
What the plan shows
Before: == Physical Plan == SortMergeJoin [customer_id], [customer_id], Inner, with an Exchange hashpartitioning(customer_id, 200) under each side. After broadcast(dim_customer): BroadcastHashJoin [customer_id], [customer_id], Inner, BuildRight, with a BroadcastExchange under the small side only. The Exchange on the big side is gone; that is the whole win.
cache() vs persist() in one line
For a DataFrame, cache() is persist(StorageLevel.MEMORY_AND_DISK); persist lets you choose the level, including serialised or replicated. Neither runs until an action, and both hold executor memory until unpersist() or the end of the job.
Deck 03, Spark Under Pressure, covers the pyspark round. Interviewers now care less about which config you'd set and more about how you diagnose a slow or failing job. The Spark UI, OOMs, skew, joins, AQE and the small-file problem, plus 8 live coding problems.
Real questions from beginner to advanced, each with a concise model answer — practice them, then rehearse live in a mock interview. Every answer is open; collapse any you have already covered.
EasyWhat is the difference between RDD, DataFrame, and Dataset?
RDD is the low-level abstraction — a distributed collection of JVM objects with functional transformations, no schema, and no optimizer support. DataFrame adds a schema and runs through Catalyst and Tungsten but is untyped at compile time; it is literally Dataset[Row]. Dataset[T], available in Scala and Java, adds compile-time type safety over case classes while keeping Catalyst optimization. Default to the DataFrame/Dataset API and drop to RDDs only for needs like custom partitioning.
EasyExplain transformations vs actions and lazy evaluation.
Transformations like map, filter, and join build up a logical plan without executing anything; actions like count, collect, and write trigger a job. Laziness lets Spark see the whole plan before running it, so Catalyst can push filters down, prune columns, and pipeline narrow operations into a single stage. A common follow-up: calling the same action twice recomputes the entire lineage unless you cache the intermediate result.
EasyWhat are narrow and wide transformations, and why do stages break at wide ones?
A narrow transformation (map, filter, union) needs only one parent partition per output partition, so it can be pipelined in memory. A wide transformation (groupByKey, a join without co-partitioning, distinct, repartition) needs data from many parent partitions, forcing a shuffle. The DAGScheduler cuts stage boundaries exactly at shuffle dependencies — every stage is a run of narrow operations between shuffles.
EasyWhat is a shuffle and why is it expensive?
A shuffle redistributes data across the cluster by key: map-side tasks write sorted, partitioned blocks to local disk, and reduce-side tasks fetch their blocks over the network. That means serialization, disk I/O, and network transfer all at once, plus a hard stage boundary. The shuffle partition count defaults to spark.sql.shuffle.partitions=200, which is rarely right for your data size until AQE coalesces it at runtime.
EasyWhy is reduceByKey preferred over groupByKey?
reduceByKey applies the combining function on the map side first, so each partition sends one partially aggregated value per key across the network. groupByKey ships every raw value, inflating shuffle size and risking OOM on hot keys. The same principle carries into DataFrames — aggregations do partial aggregation automatically, which is one more reason to prefer the SQL API over raw RDDs.
MediumWalk through what the Catalyst optimizer does to a query.
Catalyst runs four phases: analysis resolves columns and tables against the catalog; logical optimization applies rule-based rewrites like predicate pushdown, column pruning, and constant folding; physical planning generates candidate plans and selects one, including the join strategy choice between broadcast hash and sort-merge using size statistics; finally, whole-stage code generation compiles the chosen plan into JVM bytecode. This is why filter conditions written as column expressions beat opaque lambdas — Catalyst can only rewrite what it can inspect.
MediumWhat does Tungsten contribute beyond Catalyst?
Tungsten is the execution layer: it stores rows in a compact binary UnsafeRow format managed off-heap, eliminating Java object overhead and most GC pressure, and lays data out for CPU cache efficiency. Whole-stage code generation fuses a chain of operators into a single generated function, removing virtual function calls and iterator overhead. In short, Catalyst decides what plan runs; Tungsten decides how efficiently it executes.
MediumWhen does Spark choose a broadcast hash join, and when do you force one?
If one side's estimated size is under spark.sql.autoBroadcastJoinThreshold (10MB by default), Spark ships that entire table to every executor and joins without shuffling the large side. You force it with broadcast(df) or the /*+ BROADCAST */ hint when statistics are missing or wrong — common after filters whose selectivity Spark cannot estimate. Mention the failure modes: broadcasting something too big can OOM the driver, which collects the table first, and AQE can now convert a sort-merge join to broadcast at runtime once actual sizes are known.
MediumExplain Spark's persistence levels and when cache() is a mistake.
persist() takes a StorageLevel: MEMORY_ONLY (the RDD default) drops partitions that don't fit and recomputes them from lineage; MEMORY_AND_DISK (the default for DataFrame cache()) spills to disk instead; serialized, DISK_ONLY, OFF_HEAP, and replicated (_2) variants trade CPU, space, and resilience. Caching is a mistake when data is used once, when recomputation is cheaper than storage, or when cached blocks squeeze the execution memory a shuffle needs, since both share the unified pool. Always unpersist() when done.
Mediumrepartition vs coalesce — what is the actual difference?
repartition(n) always performs a full shuffle and can increase or decrease partition count with even distribution; coalesce(n) is a narrow operation that merges existing partitions without a shuffle. The trap: coalesce collapses into the previous stage, so coalesce(1) before a write can force the entire upstream computation onto a single task. For 'write fewer output files', repartition or AQE partition coalescing is usually the safer answer.
MediumWhat does Adaptive Query Execution change at runtime?
AQE re-optimizes the plan at shuffle boundaries using real statistics from completed stages. Its three headline features are coalescing small shuffle partitions (fixing the shuffle.partitions=200 guess), converting sort-merge joins to broadcast joins when a side turns out to be small, and splitting skewed partitions in joins via spark.sql.adaptive.skewJoin.enabled. It has been on by default since Spark 3.2, so lead with AQE and present manual shuffle-partition tuning as the fallback.
MediumHow does dynamic allocation work, and what does it need to be safe?
With spark.dynamicAllocation.enabled=true, Spark requests executors as tasks queue up and releases them after executorIdleTimeout, bounded by minExecutors and maxExecutors. The catch is shuffle data: a released executor takes its shuffle files with it, so you need either the external shuffle service on YARN or spark.dynamicAllocation.shuffleTracking.enabled on Kubernetes, which keeps executors holding live shuffle data alive. It is the standard setup for shared clusters running jobs with bursty stages.
HardA join is stuck at 199/200 tasks. Diagnose and fix it.
That signature is data skew — one shuffle partition holds a hot key. Confirm in the Spark UI by sorting tasks by duration and shuffle read size; a task reading many times the median is the culprit, and the key is often null or a default value. Fix in order: filter or separately handle null and junk keys, let AQE's skew-join splitting take it, broadcast the smaller side to remove the shuffle entirely, or salt — append a random suffix 0..N to the hot key on the large side, explode the small side N ways, and aggregate in two passes. Note that AQE splits skew only for joins, not aggregations, so salting still earns its place.
HardHow do you size executors on a cluster — say, nodes with 16 cores and 64GB RAM?
Reserve roughly 1 core and 1GB per node for the OS and daemons, then target about 5 cores per executor — the sweet spot for HDFS/S3 client throughput. That yields 3 executors per node at 5 cores each; dividing the remaining ~63GB by 3 gives ~21GB per executor, from which spark.executor.memoryOverhead (the larger of 384MB or 10%, more with off-heap use) must be subtracted, so set roughly 18–19GB of heap. Avoid both extremes: single-core executors lose broadcast sharing within the JVM, while one fat executor per node suffers long GC pauses and HDFS client contention.
HardExplain Spark's unified memory model and where OOMs actually come from.
Of executor heap minus a 300MB reserve, spark.memory.fraction (0.6) forms the unified pool, split between execution memory for shuffles, joins, sorts, and aggregations and storage memory for cached blocks by spark.memory.storageFraction (0.5) — but the boundary is soft: execution can evict cached blocks down to the storage floor, while storage can never evict execution. Real OOMs usually come from the other 40% — user objects, or one huge task after skew — or from memoryOverhead breaches (Netty buffers, off-heap allocations) where YARN or Kubernetes kills the container rather than throwing a Java OOM. Distinguishing a heap OOM from a container kill is the senior-level detail interviewers reward.
HardWhy can Dataset's typed API — map and filter with lambdas — be slower than DataFrame column expressions?
A Scala lambda is a black box to Catalyst: the optimizer cannot push predicates past it, prune columns it might touch, or fold it into generated code, so whole-stage codegen breaks at that operator. Worse, every row must be deserialized from Tungsten's binary UnsafeRow into your case class through the encoder and serialized back afterwards — per-row ser/de that column expressions avoid entirely. The practical rule: express logic in Column functions or Spark SQL wherever possible, and confine typed lambdas to logic that genuinely cannot be written relationally.
By company
Big Data Engineer interviews that ask these questions
See how this topic shows up in real Big Data Engineer loops — rounds, difficulty and company-specific questions:
Should I prepare Spark in Scala or PySpark for Indian interviews?
The concepts interviewers actually grade — Catalyst, shuffles, skew, memory — are language-agnostic, and most data engineering rounds accept either language. Scala still dominates older platform teams and some product companies, and Scala fluency signals depth because you can discuss encoders, Datasets, and JVM memory credibly. Prepare concepts first, then be fluent in whichever API your target roles use daily.
Are RDD questions still asked in 2026?
Yes, but conceptually rather than as coding exercises. Lineage, narrow vs wide dependencies, and reduceByKey vs groupByKey remain standard because they test whether you understand the engine underneath the DataFrame API. You are unlikely to write raw RDD code in a round, but you must explain when dropping to RDDs is justified, such as custom partitioning.
How deep do interviewers go into Spark tuning?
Past three years of experience, expect scenario-driven depth: reading task duration and shuffle metrics from the Spark UI, walking through a skew fix, and doing executor-sizing arithmetic on a stated node spec. Purely definitional answers stall out in these rounds. One genuine production war story — what broke, what the UI showed, what you changed — is worth ten memorized configs.
What do Spark-heavy data engineering roles pay in India?
This page quotes no pay figures. PrepNPlaced's India Tech Hiring Report found that fewer than 300 of its postings disclosed an INR salary, too few to publish honestly. Engineers who can debug and tune production Spark jobs are the ones product companies and GCCs pay more for, and the tuning and sizing questions on this page are the filter those interviews use.
Next Step
Turn The Guide Into Practice
Use PrepNPlaced tools to turn this learning path into resume proof, targeted practice, and interview-ready explanations.