When does merging several build steps into one earn the coarser cache invalidation that comes with it?
answer
- a step is a layer and a reuse unit
- split along volatility boundaries
- same inputs, no reason to split
- scratch output stays in its own step
- splitting after the volatile step is decoration
basics
~20 sWhen the merged work always changes together anyway, or when its intermediate output must not survive as a layer of its own. Splitting pays only where one part's inputs change far less often than the other's.
solid answer
~40 sEvery step is both a layer and a unit of reuse, so step count is a granularity decision. **Splitting** buys finer reuse: the half whose inputs rarely move keeps hitting while the volatile half re-runs. That only pays when the two halves genuinely have different volatility - splitting work that always changes together adds layers and buys nothing. **Merging** costs you that: any input to the merged step invalidates all of it. It earns its place when the pieces share the same inputs anyway, when a partial result must not persist as its own layer, or when the pieces must stay consistent with one another. The rule is to split along **volatility boundaries**, not along tidiness or aesthetics.
go deeper
Know that each build step is both a layer and the unit at which work is reused, so where you draw the boundaries changes what an edit costs.
Explain the trade in both directions: splitting protects stable work, merging contains intermediate output and keeps related work consistent.
Give volatility as the boundary rule and add the cascade check - a split placed after the volatile step buys nothing, because everything below the first miss re-runs anyway.
Resist a house rule stated as a step count; state it as a question about input volatility, so it stays correct across builds with very different shapes.
## A step is two things at once Each step in a build is a **unit of reuse** - the granularity at which the builder decides to skip work - and a **layer** in the finished image. Deciding how many steps to write is therefore not a formatting question. It decides what an edit costs and what the image is made of. ## What splitting buys Splitting separates inputs. If two pieces of work have different volatility, putting them in separate steps lets the stable one keep hitting while the volatile one re-runs: - a dependency install whose inputs move weekly, separated from a compile whose inputs move hourly; - a slow one-off preparation step, separated from anything that changes per commit; - a step that copies a rarely-edited configuration file, separated from the step that copies the source. The gain is real only when the volatility genuinely differs. Splitting two pieces of work that always change together produces two cache entries that always miss together - more layers, the same rebuild. ## What merging buys, and costs Merging gives up granularity: **any** input to the merged step invalidates all of it. That is a genuine cost, and it is the reason a fetch and a compile should rarely share a step - a source edit would then re-run the fetch as well. It earns its place in three situations: 1. **The pieces share inputs anyway.** Two commands driven by the same file will always miss and hit together, so separating them only adds layers. 2. **An intermediate result must not persist.** Whatever a step leaves behind is in that step's layer, so work that produces scratch output and then cleans it up belongs inside one step; a clean-up in a later step cannot remove what an earlier layer already recorded. 3. **The pieces must stay consistent.** Where a half-applied result would be wrong - a fetch and the verification of what it fetched, a change and its fix-up - a single unit of reuse means you never assemble an image from one half of a stale pair and one half of a fresh one. | axis | more, smaller steps | fewer, merged steps | |---|---|---| | reuse after an edit | finer - stable work keeps hitting | coarser - the whole step re-runs | | layer count | higher | lower | | intermediate output | persists in its own layer | contained inside one layer | | consistency between pieces | can diverge across runs | always produced together | ## The decision, as a rule Split along **volatility boundaries**. Ask of any two adjacent pieces of work: *do their inputs change at different rates?* If yes, split - the stable one is worth protecting. If no, merging costs nothing in reuse and may buy containment or consistency. A useful secondary check is the direction of the cascade. Because a miss invalidates everything after it, a fine split that sits **after** the volatile step is decoration - those steps all re-run anyway. Splitting is only ever worth doing **before** the volatile work, where the reuse is still live. ## The failure modes at both extremes - **Over-split:** a long chain of trivial steps whose inputs all move together. Every build misses at the same point and runs the whole tail; the only result is more layers to stack. - **Over-merged:** one enormous step that does everything. Any edit anywhere re-runs all of it, which is the forty-seconds-to-nine-minutes shape with a different cause. Both are reachable from a well-intentioned rule applied without looking at the inputs, which is why the answer an interviewer wants is *the volatility of each piece's inputs*, and not a target number of steps.
- Is there a right number of build steps to aim for?No - the number falls out of where the volatility boundaries are. A build with one slow stable input and one fast one wants a split at that seam; a build where everything moves together wants fewer, larger steps. A target step count optimises the wrong quantity.
- Why is splitting the steps after the volatile one pointless?Because a miss invalidates every step after it. Once the volatile step has re-run, the steps below it all have new parents and re-run regardless of how finely they are divided. Reuse is only live above the first miss.
saying these in an interview costs you the question
- Argues for a fixed number of steps regardless of the inputs
- Splits work whose inputs always change together
- Merges a slow dependency fetch with the per-commit compile
- Thinks more steps always mean better reuse
- Splits steps below the volatile one and expects a gain