New · Cohort 3Engineering Analytics Cohort 3 goes live 25 July — only 30 seatsRegister Now
Data Engineering Interviews

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 2026-07-25. 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.

Guide

What To Learn And How To Practice

Engine fundamentals every round opens 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

Performance tuning is 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
AQE's runtime re-optimization: partition coalescing, join switching, skew splits

Senior rounds test resource reasoning

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

Question bank

16 interview questions with answers

Real questions from beginner to advanced, each with a concise model answer — practice them, then rehearse live in a mock interview.

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.

FAQ

Common 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?

Spark remains the highest-demand skill on Indian data engineering job descriptions. Typical ranges are roughly 8–16 LPA at 2–4 years in services companies, and 20–40 LPA at product companies and GCCs for engineers who can demonstrably debug and tune production jobs. The tuning and sizing questions on this page are exactly the filter used at the higher band.

Next Step

Turn The Guide Into Practice

Use PrepNPlaced tools to turn this learning path into resume proof, targeted practice, and interview-ready explanations.

Practice AI Mock Interview