PySpark Interview Questions with Examples

PySpark interview questions test the API details that decide whether a job is correct and fast. Seven are answered on a real two-million-row electricity file that fights back: a placeholder that turns numbers into strings, dates without leading zeros that Spark 3 will not parse, and a UDF that drags rows into Python.

GloryTecks Team

Training & Placement Faculty

6 May 2026 8 min read

Last updated: 9 October 2026

Share:

PySpark interview questions move quickly from "what is a DataFrame?" to details that decide whether a job is correct: how it reads a file, what it does with missing values and dates, and when your code leaves the JVM for Python. Clean sample data hides most of these. The answers below come from a real file that does not cooperate: the UCI household power data, two million one-minute readings, separated by semicolons, with "?" for missing values and dates written without leading zeros (UCI: household power consumption).

python
import re

from pyspark.sql import SparkSession, Window, functions as F, types as T

spark = (SparkSession.builder.master("local[4]").config("spark.ui.showConsoleProgress", "false")
         .config("spark.driver.memory", "2g").getOrCreate())
spark.sparkContext.setLogLevel("ERROR")
tracker = spark.sparkContext.statusTracker()
PATH = "household_power_consumption.txt"                       # UCI household power: ';'-separated, '?' = missing


def jobs(group, action):
    spark.sparkContext.setJobGroup(group, group)
    out = action()
    return out, len(tracker.getJobIdsForGroup(group))


print("P1 schema inference and a placeholder for missing values")
inferred, n_inferred = jobs("infer", lambda: spark.read.csv(PATH, sep=";", header=True, inferSchema=True))
print(f"   inferSchema=True: Global_active_power is {dict(inferred.dtypes)['Global_active_power']} (jobs to read the schema: {n_inferred})")
with_null, _ = jobs("infer-null", lambda: spark.read.csv(PATH, sep=";", header=True, inferSchema=True, nullValue="?"))
print(f"   with nullValue='?': Global_active_power is {dict(with_null.dtypes)['Global_active_power']}")

print("\nP2 an explicit schema skips the inference pass")
schema = T.StructType([T.StructField("Date", T.StringType()), T.StructField("Time", T.StringType())]
                      + [T.StructField(c, T.DoubleType()) for c in ["Global_active_power", "Global_reactive_power", "Voltage",
                                                                    "Global_intensity", "Sub_metering_1", "Sub_metering_2", "Sub_metering_3"]])
power, n_explicit = jobs("explicit", lambda: spark.read.csv(PATH, sep=";", header=True, schema=schema, nullValue="?"))
print(f"   jobs to read with an explicit schema: {n_explicit}")

print("\nP3 nulls")
total, missing = power.count(), power.filter(F.col("Global_active_power").isNull()).count()
avg_all = power.agg(F.avg("Global_active_power")).first()[0]
avg_zero = power.fillna(0, ["Global_active_power"]).agg(F.avg("Global_active_power")).first()[0]
print(f"   {total:,} rows, {missing:,} with no reading | avg ignoring nulls {avg_all:.4f} kW | avg after fillna(0) {avg_zero:.4f} kW")

print("\nP4 parsing dates: the pattern must match the data")
sample = power.select("Date").distinct().orderBy("Date")
print(f"   example raw values: {[r.Date for r in sample.limit(3).collect()]}")
try:
    power.select(F.to_date("Date", "dd/MM/yyyy").alias("d")).filter(F.col("d").isNull()).count()
except Exception as error:                                     # Spark 3 refuses rather than silently returning nulls
    name = type(error).__name__
    reason = next(line for line in str(error).splitlines() if "could not be parsed" in line or "Fail to parse" in line)
    message = re.sub(r"'[^']*'", "'<first bad value>'", reason.split("You can set")[0].strip())   # which row fails first varies
    print(f"   'dd/MM/yyyy' -> {name}: {message}")
loose = power.select(F.to_date("Date", "d/M/yyyy").alias("d")).filter(F.col("d").isNull()).count()
print(f"   'd/M/yyyy' -> rows that fail to parse: {loose:,}")

daily = (power.withColumn("day", F.to_date("Date", "d/M/yyyy")).groupBy("day")
         .agg(F.avg("Global_active_power").alias("kw")).filter(F.col("kw").isNotNull()))
w = Window.orderBy("day").rowsBetween(-6, 0)
rolled = daily.withColumn("kw_7d", F.avg("kw").over(w)).orderBy(F.desc("kw")).first()
print(f"\nP5 window: highest daily average {rolled.kw:.3f} kW on {rolled.day}, 7-day rolling average then {rolled.kw_7d:.3f} kW")

print("\nP6 a Python UDF vs a built-in function")
watts_udf = F.udf(lambda kw: None if kw is None else kw * 1000.0, T.DoubleType())
by_udf = power.select(watts_udf("Global_active_power").alias("w"))
by_builtin = power.select((F.col("Global_active_power") * 1000).alias("w"))
same = by_udf.agg(F.sum("w")).first()[0] == by_builtin.agg(F.sum("w")).first()[0]
print(f"   same total: {same} | UDF plan runs Python (BatchEvalPython): {'BatchEvalPython' in by_udf._jdf.queryExecution().executedPlan().toString()}"
      f" | built-in plan does: {'BatchEvalPython' in by_builtin._jdf.queryExecution().executedPlan().toString()}")

print("\nP7 take() vs collect()")
print(f"   take(5) returned {len(power.take(5))} rows; collect() would pull all {total:,} rows into the driver")
spark.stop()
text
P1 schema inference and a placeholder for missing values
   inferSchema=True: Global_active_power is string (jobs to read the schema: 2)
   with nullValue='?': Global_active_power is double

P2 an explicit schema skips the inference pass
   jobs to read with an explicit schema: 0

P3 nulls
   2,075,259 rows, 25,979 with no reading | avg ignoring nulls 1.0916 kW | avg after fillna(0) 1.0779 kW

P4 parsing dates: the pattern must match the data
   example raw values: ['1/1/2007', '1/1/2008', '1/1/2009']
   'dd/MM/yyyy' -> Py4JJavaError: Fail to parse '<first bad value>' in the new parser.
   'd/M/yyyy' -> rows that fail to parse: 0

P5 window: highest daily average 3.315 kW on 2006-12-23, 7-day rolling average then 1.817 kW

P6 a Python UDF vs a built-in function
   same total: True | UDF plan runs Python (BatchEvalPython): True | built-in plan does: False

P7 take() vs collect()
   take(5) returned 5 rows; collect() would pull all 2,075,259 rows into the driver

P1. What does inferSchema do with a placeholder value?

It sees text. With inferSchema=True, the power column came back as a string, because "?" is not a number, and every later sum or average would have needed a cast or would have failed. Declaring the placeholder with nullValue="?" turned those cells into nulls, and inference then chose double (Spark: CSV options). Ask what "missing" looks like in the source before trusting inferred types.

P2. Why pass an explicit schema?

Inference reads the data to guess types, an extra pass over the file. Reading with inference launched 2 jobs before any query ran; reading with an explicit schema launched none. On large files, that pass costs real time. An explicit schema also documents what you expect, and a type that does not match becomes visible immediately instead of quietly turning a column into strings.

P3. How do aggregates treat nulls?

They skip them. 25,979 of 2,075,259 rows had no reading. The average ignoring nulls was 1.0916 kW; after fillna(0) it fell to 1.0779 kW, because 25,979 invented zeros were averaged in. Filling with zero is only right when zero is the true value, not when the value is unknown. Count nulls before and after every fill, and say which you chose and why.

P4. Why did a date pattern fail?

Because the data has no leading zeros: dates such as "1/1/2007". In Spark 3, a numeric pattern letter repeated twice, as in dd, expects exactly two digits when parsing (Spark: datetime patterns). Spark did not quietly return nulls; it raised an error: "Fail to parse ... in the new parser", naming the first value it met. The pattern d/M/yyyy parsed every row. The error mentions the change of parser in Spark 3.0 and the spark.sql.legacy.timeParserPolicy setting (Spark: migration guide). Fixing the pattern is better than switching to the legacy parser.

P5. How do you calculate a rolling average in PySpark?

With a window and a row frame: Window.orderBy("day").rowsBetween(-6, 0) averages each day with the six before it. The highest daily average was 3.315 kW on 23 December 2006, while the 7-day rolling average that day was 1.817 kW, smoothing a single cold, busy day. A window with orderBy and no partitionBy moves all rows to one partition, which is fine for 1,442 daily rows and a problem for billions; partition by a key when the data is large.

P6. Why avoid Python UDFs?

Because each row leaves the JVM, is converted, processed in Python and sent back. A UDF converting kilowatts to watts gave exactly the same total as the built-in expression col * 1000, but its physical plan contained BatchEvalPython, the step that ships rows to Python workers; the built-in plan did not. Use built-in functions first, and if Python is unavoidable, pandas UDFs, which move data in Arrow batches and work on whole columns (PySpark: Arrow and pandas UDFs).

P7. What is wrong with collect()?

It brings every row to the driver. take(5) returned 5 rows; collect() would have pulled all 2,075,259 into one Python process. On real data that is how drivers run out of memory. Use take, limit or show to inspect, and write large results to storage instead of collecting them.

P8. repartition() or coalesce()?

Both change the number of partitions. repartition(n) shuffles all rows into n roughly equal partitions, and can also partition by a column; coalesce(n) only merges existing partitions without a full shuffle, so it is cheaper but can leave partitions uneven. Use coalesce to reduce the number of output files before writing, and repartition when you need more parallelism or rows grouped by a key.

How to answer PySpark questions

  • Read deliberately: separator, header, null marker and schema.
  • Count nulls before and after every transformation that touches them.
  • Prefer built-in functions to UDFs, and check the plan.
  • Keep data on the executors; bring only small results to the driver.

Spark's internals are covered in Apache Spark interview questions and tuning in Spark performance interview questions. The same file is used with shell tools in Linux interview questions for data engineers. For when Spark is overkill, see PySpark vs pandas; for the full round, data engineering interview questions and answers.

What are common PySpark interview questions?

Reading files and schemas, null handling, date and time parsing, joins, window functions, UDFs versus built-in functions, pandas UDFs, caching, partitions, and the difference between actions like collect and take.

Should I use inferSchema in PySpark?

For exploration it is convenient, but it costs an extra pass and can mistype columns with placeholders. Use an explicit schema in production jobs.

Why are Python UDFs slow in PySpark?

Rows have to be serialised from the JVM to a Python worker and back. Built-in functions avoid this entirely, and pandas UDFs reduce it by working on Arrow batches.

How do I handle dates without leading zeros in Spark 3?

Use single-letter pattern fields such as d/M/yyyy, which accept one or two digits, instead of dd/MM/yyyy, which expect exactly two.

PySpark is part of the Data Engineering course.

Sources

GloryTecks Team

Training & Placement Faculty

The GloryTecks faculty team mentors learners across Data Science, AI and Data Engineering, with a focus on real-time projects and placement readiness in Hyderabad.

Want to master Interview Questions?

Join GloryTecks’ expert-led Data Engineering training in Hyderabad with real-time projects and 100% placement support.

Explore course