Lazy Evaluation: Beginner
Nothing Runs Until an Action
- filter, select, groupBy, join, withColumn
- Returns a new DataFrame instantly
- Runs NO work, moves NO data
- Just records what you asked for
- count, collect, show, write, take
- Demands a real result
- Forces the whole chain to execute
- This is the moment the job runs
Watch it fire all at once
(orders
.filter(F.col("status") == "Completed")
.groupBy("region")
.agg(F.sum("profit").alias("total_profit"))
.orderBy(F.col("total_profit").desc()))Why Laziness Makes Spark Fast
The same answer, less work
> From products, keep only in-stock items (in_stock = 1) and return their name and category, ordered by name. The filter and the column choice are the two operations to supply; Spark will run the filter first regardless of where you place it.
(products .(F.col("in_stock") == 1) .("product_name", "category") .orderBy("product_name"))
The Action Catalog
| Action | What it produces | Where the result goes |
|---|---|---|
| count() | The number of rows | A number, back to the driver |
| collect() | Every row, as a local list | All data, back to the driver |
| take(n) / show() | The first n rows | A few rows, back to the driver |
| write...save() | The full result on disk | Out to storage, from the executors |
| first() / head() | One row | A single row, back to the driver |
Trigger it deliberately
(order_items
.groupBy("product_id")
.agg(F.sum("quantity").alias("units_sold"))
.orderBy(F.col("units_sold").desc()))The collect() Trap
- Pulls EVERY row to the driver
- Bounded by one machine's memory
- OOMs the driver on a big result
- Use only on a known-small result
- show/take pull only a few rows
- write streams out from executors
- Never funnels all data through one node
- The right default for inspection or saving
Re-Execution on Every Action
Why it recomputes instead of remembering
| What you do | What Spark does | The cost |
|---|---|---|
| One action on the chain | Runs the chain once | Paid once, correct |
| Two actions on the same chain | Runs the whole chain twice | Paid twice, often wasteful |
| The same chain reused in a loop | Re-runs it every iteration | Paid N times, usually a bug |
- Reach for an action only when you actually need a result; each one runs the whole chain.
- Use show or take to inspect data, and write to save it, instead of collect.
- When you call several actions on one expensive chain, plan to cache the shared result.
- Read the end of a chain to find the action; that is the line that triggers the job.
- Don't assume a DataFrame caches itself after the first action; by default it recomputes.
- Don't collect() an unbounded result; the driver is one machine and will OOM.
- Don't expect a transformation to do work on its own; nothing runs until an action.
- Don't bury a show() in a loop; each call re-runs the entire pipeline.
> You are a data engineer at a streaming service computing a daily report. You build an expensive chain that joins viewing events to a content catalog and aggregates watch time per title, then you call count to log how many titles appeared and write to save the report.
You wrote a recipe. Nothing cooks until you call an action.
- Category
- Spark
- Difficulty
- beginner
- Duration
- 13 minutes
- Challenges
- 3 hands-on challenges
Topics covered: Nothing Runs Until an Action, Why Laziness Makes Spark Fast, The Action Catalog, The collect() Trap, Re-Execution on Every Action
Lesson Sections
- Nothing Runs Until an Action (concepts: paSparkExecutionModel)
In a database, when you press run, the query runs. In Spark, when you write a transformation, nothing happens. You can chain a filter onto a select onto a join onto a groupBy, building a description 10 lines long, and not a single byte of data has moved. Spark has simply written down what you asked for. The work only begins when you call an action, a special kind of method that demands an actual result: a count, the rows themselves, or a write to disk. This split is called lazy evaluation, and t
- Why Laziness Makes Spark Fast (concepts: paCatalystOptimizer)
Laziness can feel like an annoyance when you are debugging and an error only shows up 3 lines later, at the action, instead of where you wrote the typo. But it is the reason Spark is fast, and the designers chose it deliberately. Because Spark sees your entire chain before it runs anything, it can look at the whole plan and rearrange it for efficiency, the same way a good query optimizer does for SQL. Think about what an eager system would have to do. If every transformation ran the instant you
- The Action Catalog (concepts: paSparkExecutionModel)
If actions are the only thing that runs a job, then knowing which calls are actions is what lets you predict your job's behaviour instead of being surprised by it. The list is short and the logic is consistent: an action is any call that needs to produce a concrete result outside the lazy DataFrame world, either a value back in your program or bytes written to storage. Everything not on that list, the filters and selects and joins and groupBys you build your logic from, is a transformation, and
- The collect() Trap (concepts: paMemoryManagement)
One action deserves its own section because it is a common way to take down a Spark job: collect. It collects every row of your result and pulls it back to the driver as a local list. On a small result that is fine and useful. On a large one it is a disaster, because the driver is a single machine with a single machine's memory, and you are asking it to hold data that was spread across the whole cluster because it did not fit on one machine. The failure mode is abrupt and recognisable. The job r
- Re-Execution on Every Action (concepts: paSparkCaching)
Here is a consequence of laziness that surprises almost everyone, and it is the bridge to the next big topic. A DataFrame remembers how to produce itself, not the data it produces. So when you call two actions on the same chain, Spark runs the entire chain twice, once for each action. It does not quietly remember the result from the first action and reuse it. It recomputes from the original source every single time you ask. Say you build an expensive chain, a big join followed by an aggregation,