Review core architectures, operations, shuffles, optimizations, and syntax cheat sheets within 15 minutes before your interview.
Apache Spark is an in-memory, distributed general-purpose data engine. It runs operations in worker memory nodes instead of writing files to local disk spindles between phases, making it up to 100 times faster than Hadoop MapReduce.
| Concept | Revision Summary |
|---|---|
| Driver Node | The main orchestrator process that runs the main() method, creates SparkSession, plans logical operations, schedules tasks, and coordinates worker executors. |
| Executors | Worker processes running on worker machines. They store dataset partitions in local RAM memory and run physical tasks. |
| Partitions | The logical chunks that a large dataset is divided into. Each executor thread processes one partition at a time. |
This map illustrates how the Driver coordinates with the Cluster Manager (like YARN or Kubernetes) to allocate worker Executors across multiple physical server machines.
When you run a Spark job, it travels through several stages of transformation: from parsing raw code into logical graphs to executing physical worker tasks.
from pyspark.sql import SparkSession
from pyspark.sql.functions import col
# Create Spark Session
spark = SparkSession.builder.appName("ETL").getOrCreate()
# Read CSV data with options
df = spark.read.csv("data.csv", header=True, inferSchema=True)
# Select, filter, and calculate bonus using parentheses chain
result = (df.select("name", "salary")
.filter(col("salary") > 80000)
.withColumn("bonus", col("salary") * 0.1))
# Write output partitioned by department
result.write.mode("overwrite").partitionBy("department").parquet("output.parquet")# Read as Resilient Distributed Dataset (RDD)
rdd = spark.sparkContext.textFile("logs.txt")
# Chain map operations to perform word count
words = (rdd.flatMap(lambda line: line.split(" "))
.map(lambda word: (word, 1))
.reduceByKey(lambda a, b: a + b))
# Collect results to a local Python list
output = words.collect()# Create a Temporary Table View
df.createOrReplaceTempView("employees")
# Execute standard SQL query
sql_df = spark.sql("SELECT department, AVG(salary) as avg_sal FROM employees GROUP BY department HAVING avg_sal > 90000")
sql_df.show()from pyspark.sql.window import Window
from pyspark.sql.functions import col, dense_rank, lag
# Define partition boundaries and ordering
windowSpec = Window.partitionBy("department").orderBy(col("salary").desc())
# Apply rank and lookback value over window specifications
ranked_df = (df.withColumn("rank", dense_rank().over(windowSpec))
.withColumn("prev_sal", lag("salary", 1).over(windowSpec)))Understand the difference between lazy transformations (which construct logic) and actions (which trigger data execution).
| select() | Project column layouts. |
| filter() | Filter rows on conditions. |
| groupBy() | Group records (triggers shuffles). |
| join() | Join relational tables. |
| show() | Print lines to console. |
| count() | Return integer total. |
| collect() | Send all rows to driver RAM. |
| write() | Trigger folder output write steps. |
# Read APIs
spark.read.parquet("folder/*.parquet")
spark.read.json("logs.json")
# Write APIs with Modes
df.write.mode("overwrite").csv("target_dir")
df.write.mode("append").saveAsTable("hive_db.table_name")Choosing the right join type determines the amount of data transferred across the cluster network.
| Join Type | Behavior | Shuffle Tradeoff |
|---|---|---|
| Inner | Keeps matched rows in both datasets. | Requires sorting/shuffling unless pre-bucketed. |
| Left / Right | Preserves rows on outer side, fills NULLs on unmatched inner. | SortMergeJoin triggers standard network shuffles. |
| Left Anti | Keeps rows on left table that have NO match on right. | Optimized way to isolate delta changes. |
| Broadcast | Replicates smaller table to worker memory blocks. | Zero Network Shuffling on the big table. Highly performant. |
These performance guidelines are crucial to understand for writing efficient jobs and answering design questions.
spark.serializer to KryoSerializer. It is up to 10x faster and more compact than standard Java serialization.Shuffle is the process of redistributing data across executors. It is triggered by wide operations (e.g. groupBy, join, distinct, repartition).
Transformations are divided into narrow (independent) and wide (dependent) categories based on their shuffle dependencies.
| Metric | Narrow Transformation | Wide Transformation |
|---|---|---|
| Definition | Each input partition maps to exactly one output partition. | Multiple input partitions contribute to single output partitions. |
| Network Shuffle | No Shuffle required. Tasks run locally in parallel. | Triggers network shuffle. High latency operations. |
| Examples | select(), filter(), map(), flatMap(), union() | groupBy(), join(), distinct(), repartition(), coalesce() |
Use caching to save intermediate calculations if a DataFrame is referenced multiple times in a session workflow.
| Feature | cache() | persist() |
|---|---|---|
| Storage Level | Hardcoded to memory storage (MEMORY_AND_DISK). | Accepts custom StorageLevel objects. |
| Options | None. Simple default shortcut. | MEMORY_ONLY, DISK_ONLY, MEMORY_ONLY_SER, etc. |
Repartitioning reorganizes data entirely, whereas Coalesce merges partitions without shuffling.
| Aspect | repartition() | coalesce() |
|---|---|---|
| Operation Size | Can increase or decrease partition count. | Can only decrease partition count. |
| Shuffle | Full Shuffle. Distributes data evenly across network. | No Shuffle. Merges local partition blocks. |
| Best For | Balancing skewed partitions before joins. | Shrinking partition sizes right before writing files. |
from pyspark.sql.functions import broadcast
# Join small table lookup (under 10MB) to large table
# Replicates users lookup to executor memory, bypassing shuffles
optimized_df = transactions.join(broadcast(users), on="user_id", how="inner")Spark transformations are evaluated lazily. This means Spark registers operations in a logical DAG without executing them immediately. Execution is deferred until an Action (like show, count, collect) is called.
This allows the Catalyst Optimizer to inspect the complete lineage graph, combining filter operations or pushing down projections to minimize physical data loading.
This plan flow shows how Spark translates code instructions into an optimized physical execution schedule.
Enabled by default in Spark 3, AQE re-optimizes logical execution plans dynamically based on runtime metrics collected during shuffle stages.
Select a common data engineering interview scenario prompt below to see how a Senior Data Engineer would structure their explanation:
Weekly data volume spikes on Monday mornings due to weekend accumulation, triggering data skew on popular keys.