← Back to list

Pyspark Coding Interview Question : Here's What Senior Data Engineers Actually Do

You have a static Python dictionary with 195 country-to-continent mappings. You need to enrich a transactional dataset by mapping…

Sriw World of Coding in T3CH · 2026-07-16 01:46 · 58 claps · 7.8 min read paywalled
#spark-interview-question #spark-optimization #coding #delta-lake #data-engineer-interview
Open on Medium ↗
Wiki topics: 💻 · Programming 🔧 · Data Engineering

Pyspark Coding Interview Question : Here's What Senior Data Engineers Actually Do

You have a static Python dictionary with 195 country-to-continent mappings. You need to enrich a transactional dataset by mapping country_code to continent. Would you loop through the dictionary and generate nested .withColumn(F.when(...)) statements?

A real interview question that exposes who actually understands Spark’s internals — and who just memorized syntax

🔥 The Question That Breaks Most “Experienced” PySpark Developers

Picture this. You’re in a data engineering interview. The interviewer leans in and asks:

“You have a static Python dictionary with 195 country-to-continent mappings. You need to enrich a transactional dataset by mapping country_code to continent. Would you loop through the dictionary and generate nested .withColumn(F.when(...)) statements?"

Most candidates say yes. Confidently. They write a for loop, chain 195 .when() conditions, and submit it as their answer.

The senior engineer in the room just winced.

If you’ve ever written a loop that builds a giant chain of .when() conditions in PySpark, this article is going to change how you think about PySpark dictionary mapping forever. By the end, you'll know exactly why that approach silently sabotages your pipeline — and the two production-grade patterns that replace it.

🎯 The Problem: Why This Innocent-Looking Code Is a Trap

Here’s the code almost everyone writes first:

from pyspark.sql import functions as F

country_to_continent = {
    "US": "North America",
    "IN": "Asia",
    "JP": "Asia",
    "DE": "Europe",
    "BR": "South America",
    # ... 190 more entries
}
def build_when_chain(mapping, column):
    items = iter(mapping.items())
    code, continent = next(items)
    expr = F.when(F.col(column) == code, F.lit(continent))
    for code, continent in items:
        expr = expr.when(F.col(column) == code, F.lit(continent))
    return expr.otherwise(F.lit("Unknown"))
df_enriched = df.withColumn(
    "continent",
    build_when_chain(country_to_continent, "country_code")
)

It runs. It even returns the correct output on a small sample. So what’s wrong?

The problem isn’t correctness — it’s architecture. This pattern quietly breaks down in four specific ways once it hits production-scale data and production-scale dictionaries:

  • Catalyst’s analyzer chokes on deep expression trees. Every .when() you chain becomes a nested node in Spark's internal logical plan tree, and Spark's analyzer/optimizer traverses that tree recursively.
  • Whole-stage codegen has a hard limit. Spark compiles expressions into JVM bytecode, and a single Java method cannot exceed 64KB. A 195-branch CASE WHEN routinely blows past this, forcing Spark to fall back to slower interpreted execution — or throw the infamous "Generated code exceeds 64 KB" error.
  • Plan analysis time grows non-linearly. With a handful of conditions, you won’t notice. With hundreds — or if a junior engineer later “helpfully” extends the dictionary to thousands of SKU codes — compile time and driver memory usage spike disproportionately.
  • It’s the wrong tool conceptually. You’re modeling a key-value lookup using a sequential conditional chain. That’s like searching a phone book by checking every single name one at a time instead of jumping straight to the page.

This matters in the real world because enrichment-by-dictionary is everywhere — currency codes, product categories, region mappings, status code translations. Get this pattern wrong once, and it becomes copy-pasted technical debt across a dozen pipelines.

💡 The Concept: Two Architectural Patterns That Actually Scale

Instead of asking “how do I write a when() chain," the right question is: "What is the most efficient data structure Spark has for key-value lookups?"

PySpark gives you two solid answers, and which one you pick depends on the size and lifecycle of your mapping.

Pattern 1: create_map() — The Single-Expression Lookup

Spark SQL has a native MapType. Instead of building 195 conditional branches, you build one map literal column and index into it — just like a Python dictionary lookup.

Think of it like the difference between:

  • Asking 195 yes/no questions to find someone’s age (when chain), versus
  • Having a single phonebook where you look up the name directly (create_map)

Pattern 2: Broadcast Join — The Reference-Table Lookup

For larger or frequently-changing mappings, you treat the dictionary as what it really is: a small dimension table. You convert it into a tiny DataFrame and join it against your main dataset — telling Spark explicitly to broadcast it, so it’s copied to every executor instead of triggering an expensive shuffle.

This is the same mental model as a database JOIN against a lookup table — except Spark optimizes it specifically because the table is small enough to fit in executor memory.

🧪 Practical Example: Step-by-Step Implementation

Let’s build both solutions for our exact interview scenario — 195 country codes mapping to continents.

Step 1: Setup

from pyspark.sql import functions as F
from itertools import chain

data = [("TXN001", "US"), ("TXN002", "IN"), ("TXN003", "JP"), ("TXN004", "ZZ")]
df = spark.createDataFrame(data, ["transaction_id", "country_code"])
country_to_continent = {
    "US": "North America",
    "IN": "Asia",
    "JP": "Asia",
    "DE": "Europe",
    "BR": "South America",
    # ... remaining 190 entries
}

Step 2: The create_map() Solution

mapping_expr = F.create_map(
    [F.lit(x) for x in chain(*country_to_continent.items())]
)

df_enriched = df.withColumn(
    "continent",
    F.coalesce(mapping_expr.getItem(F.col("country_code")), F.lit("Unknown"))
)

df_enriched.show()

What’s happening, line by line:

  • chain(*country_to_continent.items()) flattens {"US": "North America", ...} into ["US", "North America", "IN", "Asia", ...] — the exact key-value-key-value format create_map() expects.
  • F.lit(x) for x in ... wraps every key and value as a Spark literal column.
  • F.create_map([...]) builds one single MapType column expression — not 195 separate ones.
  • .getItem(F.col("country_code")) performs an O(1)-style map lookup per row, the same way Python's dict[key] works.
  • F.coalesce(..., F.lit("Unknown")) safely handles country codes not present in the dictionary (like "ZZ" in our sample data), avoiding silent nulls.

Expected output:

One expression. One node in the query plan. No 64KB codegen risk.

Step 3: The Broadcast Join Solution (For Larger or Dynamic Mappings)

mapping_df = spark.createDataFrame(
    list(country_to_continent.items()),
    ["country_code", "continent"]
)

df_enriched = df.join(
    F.broadcast(mapping_df),
    on="country_code",
    how="left"
).fillna({"continent": "Unknown"})

df_enriched.show()

What’s happening here:

  • spark.createDataFrame(list(...)) turns your dictionary into a real two-column DataFrame — conceptually a tiny dimension table.
  • F.broadcast(mapping_df) is the critical line: it tells Spark's optimizer to physically copy this small table to every executor, enabling a broadcast hash join instead of a shuffle join.
  • how="left" preserves every transaction row even when a country code has no match.
  • .fillna(...) handles unmapped codes, same as the coalesce step above.

This produces identical output to Pattern 1 — but scales better if your “dictionary” grows into the thousands of rows, or if you’d rather store it in a managed table (e.g., a Delta or Hive reference table) that gets updated independently of your code.

⚠️ Common Mistakes & Misconceptions

Mistake 1: Reaching for a UDF instead.

@F.udf("string")
def map_continent(code):
    return country_to_continent.get(code, "Unknown")

This works, but it’s a step backward. Python UDFs force row-by-row serialization between the JVM and Python, and Catalyst can’t optimize, predicate-pushdown, or vectorize through a black-box function. It’s almost always slower than create_map() for this use case.

Mistake 2: Forgetting .otherwise() or coalesce(). Beginners assume every incoming value will match a dictionary key. In real data, it never does. Without a default value, unmatched rows silently become null, and that null can quietly propagate into downstream aggregations or joins.

Mistake 3: Joining without broadcasting. If you build the lookup DataFrame but skip F.broadcast(), Spark may decide to shuffle your large transactional dataset to match it against the tiny lookup table — turning a microsecond operation into a full shuffle stage.

Mistake 4: Looping .withColumn() calls believing each one is "cheap." Each .withColumn() call is lazy, so it feels free. But every call adds a new node to the logical plan. Chain enough of them — especially combined with chained when() calls — and you risk StackOverflowError during plan analysis on large dictionaries, because Spark's analyzer traverses these trees recursively.

🚀 Pro Tips & Best Practices

  • Rule of thumb: Under ~1,000 static entries that rarely change → use create_map(). It avoids any join, any shuffle, and any external I/O.
  • Rule of thumb: Larger, frequently updated, or business-owned mappings → use a broadcast join against a real reference table (Delta/Hive/Parquet), so updates don’t require a code deployment.
  • Always check your query plan. Run df_enriched.explain(True) and watch for unexpectedly deep Project or CASE WHEN trees — that's your early warning signal.
  • Watch executor logs for codegen fallbacks. If you see “Generated code exceeds 64 KB”, it’s confirmation your expression tree is too large for whole-stage codegen.
  • Never hardcode large dictionaries inline in production code. Externalize them — even a small CSV or table loaded once at job start is more maintainable than 195 lines buried in a script.
  • Benchmark both patterns on your actual data volume. At small scale, the difference is invisible. At billions of rows, create_map() vs. when() chains can mean the difference between a 2-minute job and a job that never finishes compiling.

🔄 Real-World Use Cases

This exact pattern shows up constantly in production data engineering:

  • Fintech ETL: mapping currency codes (USD, INR, JPY) to currency names or exchange-rate groups before settlement reporting.
  • E-commerce: mapping SKU prefixes or category codes to human-readable product categories for dashboards.
  • Telecom/IP enrichment: mapping IP ranges or country codes to regions for fraud detection and compliance reporting.
  • HR/People Analytics platforms: mapping department codes or job-level codes to standardized labels across merged datasets from multiple source systems.

It’s also a favorite interview question precisely because it separates engineers who know Spark syntax from engineers who understand Spark’s execution engine — Catalyst’s optimizer, whole-stage codegen, and broadcast joins.

📌 Quick Recap

  • ❌ Looping to generate nested .when() / .withColumn() chains creates deep expression trees that strain Catalyst's analyzer and risk the 64KB codegen limit.
  • ✅ For static, moderate-size dictionaries → use **F.create_map()** for a single-expression, O(1)-style lookup.
  • ✅ For larger or dynamic mappings → convert the dictionary into a small DataFrame and use a broadcast join.
  • 🚫 Avoid UDFs for simple key-value mapping — they bypass Catalyst’s optimizations entirely.
  • 🛡️ Always handle unmatched keys explicitly with coalesce() or fillna() to avoid silent nulls.
  • 🔍 Use .explain() to sanity-check your query plan before deploying to production scale.

🚀 Level Up Your Career — Don’t Wait, Start NOW!

If you’re serious about growing in tech and staying ahead of the curve, this is your moment. No shortcuts — just real skills that actually make a difference.

🌐 Let’s Connect & Grow Together

Follow me for practical insights, real-world learning, and career tips:

🐦 Twitter: https://x.com/SriwWorld 📺 YouTube: https://www.youtube.com/@sriwworldofcoding?sub_confirmation=1 ✍️ Medium: https://medium.com/@sriwworldofcoding 🧵 Threads: https://www.threads.com/@sriwworldofcoding 📸 Instagram: https://www.instagram.com/sriwworldofcoding/ 📘 Facebook: https://www.facebook.com/profile.php?id=61576419014220 🌌 Bluesky: https://bsky.app/profile/sriwworldofcoding.bsky.social

🎯 Want Real Skills? Start With These Hands-On Courses

⚙️ Apache Airflow Bootcamp (Workflow Automation)

👉 https://www.udemy.com/course/apache-airflow-bootcamp-hands-on-workflow-automation/ 💡 Go from beginner to advanced — master DAGs, scheduling, operators, sensors, and build real production workflows.

🔥 PySpark for Data Engineers (Architecture + Interviews)

👉 https://www.udemy.com/course/pyspark-for-data-engineers-architecture-interviews/ 💡 Deep dive into Spark architecture, optimization, and performance tuning — plus crack interviews with confidence.

🗄️ Crack Database Engineer Interviews: The Ultimate Q&A Guide

👉 https://www.udemy.com/course/crack-database-engineer-interviews-ultimate-qa-guide/?referralCode=F602538D081C73BFB375

💡 Master Database Engineer interviews with real company interview questions, detailed explanations, SQL coding challenges, database design scenarios, system design concepts, troubleshooting techniques, and leadership & behavioral interview questions.

☁️ Crack Azure Data Engineer Interviews: The Ultimate Q&A Guide

👉 https://www.udemy.com/course/crack-azure-data-engineer-interviews-the-ultimate-qa-guide/ 💡 Get interview-ready with real-world questions on ADF, Synapse, Databricks, Event Hubs, Data Lake, Azure Functions & more.

💥 The difference between where you are and where you want to be? ACTION. Start learning today — your future self will thank you.

Got a trickier Spark optimization question you’ve faced in an interview? Drop it in the comments — I might turn it into the next deep dive.


메타데이터
post_id
a28131ea8272
slug
pyspark-coding-interview-question-heres-what-senior-data-engineers-actually-do-a28131ea8272
url
https://medium.com/h7w/pyspark-coding-interview-question-heres-what-senior-data-engineers-actually-do-a28131ea8272
canonical_url
https://medium.com/h7w/pyspark-coding-interview-question-heres-what-senior-data-engineers-actually-do-a28131ea8272
author_url
https://medium.com/@sriwworldofcoding
status
ok
fetched_at
2026-09-02 12:45:16