explode is the function you came here for. It takes an array column and produces one output row per element, copying the other columns down — turning a nested array back into a flat, joinable, groupable table. It's the bridge between nested and relational shapes.
Signature
def explode(e: Column): Column // array → rows; drops empty/null arrays
def explode_outer(e: Column): Column // keeps a null row for empty/null arrays
def posexplode(e: Column): Column // adds the element's position (0-based)
def posexplode_outer(e: Column): Column
1. Explode an array column (basic)
Scala
import org.apache.spark.sql.functions.explode
carts.select(
$"name",
explode($"items").as("item")
).show(false)each array element becomes its own row; name is copied down
baked output — Spark-only type, not runnable in the browser engine
2. explode inside select vs withColumn
You can explode in a select (above) or with withColumn — same result. withColumn keeps all existing columns and adds the exploded one, which is handy when you have many columns to preserve:
Scala
// keeps id, name, items AND adds item
carts.withColumn("item", explode($"items"))
.select("id", "name", "item")
.show(3, false)explode via withColumn — the original items array is still on the row too
baked output — Spark-only type, not runnable in the browser engine
3. explode vs explode_outer
Plain explode drops rows whose array is empty or null — a silent data-loss trap. explode_outer keeps one row with a null element instead. Imagine a fourth cart with no items:
Scala
import org.apache.spark.sql.functions.explode_outer
// Leo has an empty cart: (4, "Leo", Seq())
withEmpty.select($"name", explode_outer($"items").as("item")).show(false)explode_outer keeps Leo (empty cart) with a null item; plain explode would drop him entirely
baked output — Spark-only type, not runnable in the browser engine
4. posexplode — get the index too
Scala
import org.apache.spark.sql.functions.posexplode
carts.select(
$"name",
posexplode($"items") // yields two columns: pos, col
).show(false)
posexplode adds a 0-based position column (default names: pos, col)
baked output — Spark-only type, not runnable in the browser engine
5. explode then aggregate
The most common pipeline: explode to flatten, then groupBy the element and count — "how many carts contain each item?"
Scala
carts.select(explode($"items").as("item"))
.groupBy($"item")
.count()
.orderBy($"count".desc)
.show(false)explode → groupBy → count: mug appears 3 times across all carts
baked output — Spark-only type, not runnable in the browser engine
Two gotchas. (1) You can only explode one array per select — exploding two arrays in the same select is a cross-product and usually a bug; chain two separate explodes or use arrays_zip first. (2) explode multiplies your row count, so it can massively inflate data before a shuffle — filter the array (array_contains, higher-order filter) before exploding when you can.