The Layout Inspector shows you a tree you didn't write
Layout Inspector renders the composition as an inspectable tree with per-composable recomposition and skip counts. Knowing what it can and cannot tell you is the difference between a diagnosis and a guess.

Day 93 of 100. Day 83 placed this tool among four; today it gets read properly, because most of its value is in details people skip past.
The symptom
An inspector session that produces no conclusion:
"
OrderRowshows 340 recompositions.LazyColumnshows 1.Columnshows 340.Textshows 340. Which one is the problem?"
Four numbers, no obvious next step. The instinct is to optimise the one with the biggest number, which here is a tie between three composables that are all doing the same thing — being re-invoked by something above them.
Why the obvious reading fails
The obvious reading is that a high count means that composable is expensive.
It doesn't. A count says a body ran; it says nothing about cost, and — crucially — nothing about why. Three of those four numbers are consequences of the fourth, and the tool doesn't order them for you.
The reading that produces an answer needs the second column.

The actual mechanism
Layout Inspector attaches to a debuggable process and renders the composition as a tree, with three things worth knowing about what you're looking at.
The tree isn't your source. Modifiers appear as nodes, inlined composables may be
collapsed, and the library's internal composables are interleaved with yours. A Card in
your code is several nodes here. The filter for "show only my code" is the first thing to
turn on.
Every node carries its parameters, including the modifier chain and the resolved values. This is the fastest way to answer "what is actually being passed to this thing" — faster than a log, and it shows the values after defaults are applied.
Two counts per composable, and the pair is the whole diagnostic:
| Recompositions | Skips | Reading |
|---|---|---|
| High | 0 | It cannot skip — unstable parameter, or a new instance each time (Days 79–80) |
| High | High | It's being invoked repeatedly — a parent is invalidating (Day 7) |
| Low | Low | Not the problem |
Applied to the four numbers above: whichever ancestor shows a high count with few skips below it is where the invalidation starts. The children showing high counts with high skips are just being re-invoked, and optimising them changes nothing.
Reset before you measure
The single most-skipped step, and it invalidates most sessions.
Counts accumulate from the moment the inspector attaches, so they include the initial composition of every screen you've visited. A number that includes app startup tells you nothing about a scroll.
The workflow that produces usable numbers:
- Attach, navigate to the screen, let it settle.
- Reset the counts.
- Perform the one interaction you're investigating — one scroll, one toggle.
- Read.
Without step 2 you're reading a lifetime total, which is why a LazyColumn item appearing
to have "300 recompositions" is usually 300 different rows composed once each.
What it costs
Worth being explicit, since it changes what conclusions are safe:
It requires a debuggable build, which Day 78 established is not representative — live literals turn every constant into a state read, and R8 isn't applied. Counts are directionally useful; timings are not.
The instrumentation itself has overhead. Enabling recomposition counting slows the app, so "it feels janky with the inspector attached" is not evidence of anything.
Both are reasons the tool localises a problem rather than confirming one. Day 83's order stands: Macrobenchmark says whether there's a problem, the inspector says where.
The other views
Three panels people don't open:
3D mode rotates the tree to show layering. It's how you find an invisible view
intercepting touches, or a Box stacking children in an unintended order — the kind of bug
that's invisible in a flat screenshot. It's also the quickest way to see that
AnimatedVisibility is still holding an exited element (Day 64), since the node is there in
the tree with nothing visible on screen.
Component tree filtering by recomposition count sorts the tree by activity, which turns a large screen into a short list of candidates. Combined with the source-only filter, it usually reduces a hundred-node tree to three or four worth looking at — which is the tool's genuine strength, and the reason to reach for it before a trace when you have no hypothesis yet.
The attributes panel's modifier list shows the chain in resolved order, which is the
practical way to debug Day 20's ordering problems — you can see that clip came after
background rather than reasoning about it.
It's also the fastest way to answer a layout question the counts can't touch: why is this element 200dp wide? The panel shows the constraints it received and the size it reported — Day 21's negotiation, made visible rather than inferred.
The alternative when the tool is too heavy
For a specific hypothesis, three lines beat attaching a profiler:
@Composable
fun OrderRow(order: Order) {
SideEffect { Log.d("Recompose", "OrderRow ${order.id}") }
…
}
Works in any build, no attach step, no overhead worth worrying about, and it answers "is this running when I think it is" immediately.
A slightly richer version counts without logging every line:
@Composable
fun RecompositionCounter(tag: String) {
val count = remember { mutableIntStateOf(0) }
SideEffect { count.intValue++ }
Log.d("Recompose", "$tag: ${count.intValue}")
}
The inspector is better when you don't yet know where to look. The log is better once you have a suspect. Both beat guessing, which is the option people actually take most often.
How to prove it
Take a screen you believe is fine and run the reset-then-interact loop on it. Two useful outcomes: either the counts are low, which is a fact worth having, or something is recomposing that you didn't expect — which is usually a value read too high up (Day 7) or a lambda allocation defeating skipping (Day 80).
Then do the same on a screen with a known problem and check that the two-column reading points at the file you'd already identified. Calibrating the tool against a case you understand is what makes it trustworthy on one you don't.
What this generalizes to
The principle is a measurement needs a baseline and a scope. Counts since attach, on a debug build, across every screen you've visited, are a number without either. Reset, one interaction, one screen — and the same number becomes evidence.
That's true of every profiler in every ecosystem, and it's the step most often skipped because the tool happily shows a number without it. A number that's easy to obtain and hard to interpret is worse than no number, because it feels like data.
The habit that follows: before reading any measurement, say out loud what would count as normal. "This row should compose once per appearance" turns 340 from an alarming number into a specific question about what's invoking it 339 extra times.
Tomorrow, Day 94 closes the tools pillar with lint and the editor actions — the checks that run without you asking.
Day 93 of a 100-day series on Jetpack Compose, working through the official documentation in order. Source: Layout Inspector.