Skip to content
Back to skills

Compose Internals

ASecurity

Use when writing, modifying, or reviewing Jetpack Compose UI code — @Composable functions, recomposition and skipping, Compose state (remember / MutableState), side effects in Compose (LaunchedEffect, DisposableEffect, rememberCoroutineScope, SideEffect, produceState, snapshotFlow), stability (@Stable / @Immutable), state hoisting / UDF, modifiers, declarative Compose style. Distilled from "Jetpack Compose Internals" — the rules digest is mandatory reading before any @Composable is written.

  • 2 stars
  • 0 votes
  • 0 copies
  • 3 views
  • Added September 2, 2026
toolsgokotlinreactnode

Security analysis

A100/100

Pro scans all 14 files and shows the line behind each finding

Scanned September 2, 2026

npx -y skills add gorban-dev/gor-mobile --skill compose-internals --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Compose Internals?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Compose Internals
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/gorban-dev-compose-internals/badge)](https://www.skillsdirectory.com/skills/gorban-dev-compose-internals)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: compose-internals
description: Use when writing, modifying, or reviewing Jetpack Compose UI code — @Composable functions, recomposition and skipping, Compose state (remember / MutableState), side effects in Compose (LaunchedEffect, DisposableEffect, rememberCoroutineScope, SideEffect, produceState, snapshotFlow), stability (@Stable / @Immutable), state hoisting / UDF, modifiers, declarative Compose style. Distilled from "Jetpack Compose Internals" — the rules digest is mandatory reading before any @Composable is written.
---

# Jetpack Compose Internals — rules digest

Mandatory ground rules for any code that creates or modifies a `@Composable`.
Read this digest fully; open the reference file for the topic your task
touches; check your shape against `examples/*.kt`.

## The 9 properties of composable functions

The runtime optimizes (skipping, parallel composition, reordering,
memoization) only because it can assume these properties. Code that breaks
one takes the optimization down with it.

1. **Calling context** — a composable can be called only from another
   composable: the compiler appends an implicit `Composer` parameter and
   forwards it down every call; bodies use it to emit changes to the runtime.
2. **Idempotent** — re-executing with the same inputs must emit the same
   node tree; recomposition and skipping are built on this assumption.
3. **Free of uncontrolled side effects** — the body depends on its
   parameters alone; anything touching the outside world goes through an
   effect handler, which ties it to the composable lifecycle.
4. **Any execution order** — the runtime may run sibling composables in
   whatever order suits it (e.g. by priority); never let one sibling depend
   on another having run first.
5. **Parallel-safe** — recompositions may be offloaded to different threads
   to use multiple cores; bodies must tolerate concurrent execution.
6. **Restartable and reactive** — a body re-executes whenever state it read
   changes, any number of times; the compiler generates restart code for
   every composable that reads state.
7. **Fast execution** — composables do not build UI; they emit data into an
   in-memory structure. Bodies may run every animation frame, so they must
   stay cheap; heavy work goes to a coroutine behind an effect handler.
8. **Skippable** — during recomposition, a call whose arguments are stable
   and unchanged is skipped; a restartable composable should also be
   skippable.
9. **Positional memoization** — identity comes from the call-site position
   in the sources, plus call order inside loops; `remember` and `key(...)`
   are the developer-facing handles on this mechanism.

## State rules

One line each — the linked reference carries the full rule set and mechanics.

- **Stability** — a call is skipped only when every argument is stable and
  compares equal; model inputs as all-`val` data classes of stable types;
  never take stdlib `List`/`Set`/`Map` as parameters (use
  kotlinx.collections.immutable or an `@Immutable` wrapper) →
  `references/stability.md`.
- **Hoisting / UDF** — state down as read-only values, events up as
  callbacks; state lives in the smallest common scope of its readers and
  writers; never pass `ViewModel` or `MutableState<T>` down the tree →
  `references/state-hoisting.md`.
- **Snapshot & threading** — observable data lives in `mutableStateOf` and
  friends (a plain field write invalidates nothing); a read subscribes only
  inside an observed scope (composition, `snapshotFlow`, `derivedStateOf`);
  group related writes with `Snapshot.withMutableSnapshot`; any thread may
  write, but colliding writes on a default-policy object throw at apply →
  `references/snapshot-state.md`.
- **remember semantics** — `remember` is a per-call-site slot, not a shared
  cache: a different parent means a fresh value; one owner remembers,
  children receive parameters → `references/recomposition.md`.
- **Deferred reads** — pass `() -> T` instead of `T` for fast-changing
  values, so only the scope that invokes the lambda recomposes →
  `references/state-hoisting.md`.

## Anti-patterns

### 1. Side effect in the composable body

Never run an effect directly in the body — use the matching effect handler.

```kotlin
// ❌ fires on every recomposition, from any thread, never cancelled
val news = service.fetchHeadlines()

// ✅ owned by the composition: cancelled on leave, relaunched on key change
LaunchedEffect(Unit) { news = service.fetchHeadlines() }
```

Why: breaks idempotence and effect-freedom — the runtime re-executes bodies
at will, so an in-body effect fires an uncontrolled number of times.

### 2. State without `remember`

Every state object created in a body must be remembered.

```kotlin
// ❌ fresh state instance on every pass — the value resets each recomposition
val count = mutableStateOf(0)

// ✅ stored in the slot table; survives re-execution
var count by remember { mutableStateOf(0) }
```

Why: a restartable body re-runs at arbitrary times; only `remember` writes
the value into the slot table so it outlives the invocation.

### 3. Dynamic list without `key(...)`

```kotlin
// ❌ identity = call order; inserting at the top recomposes every row below
for (task in tasks) { TaskRow(task) }

// ✅ identity travels with the data; rows keep state and skip on reorder
for (task in tasks) { key(task.id) { TaskRow(task) } }
```

Why: inside a loop positional memoization falls back to call order, so an
insert or reorder shifts every identity after that point.

### 4. Writing shared or global state from composition

Mutate state only in event callbacks or effect handlers, never mid-body.

```kotlin
// ❌ publishes from a composition that may be re-run, reordered, or discarded
legacyMap.zoomEnabled = uiState.editing

// ✅ runs after every successful composition only
SideEffect { legacyMap.zoomEnabled = uiState.editing }
```

Why: siblings may run in any order or in parallel, and a failed composition
is thrown away — an in-body write leaks from passes that never applied.

### 5. Heavy computation in the body

```kotlin
// ❌ re-sorted on every recomposition of the enclosing scope
val ranked = players.sortedByDescending { it.score }

// ✅ cached; recomputed only when players changes
val ranked = remember(players) { players.sortedByDescending { it.score } }
```

Why: violates fast execution — bodies can re-run every frame; truly heavy
work belongs in a coroutine behind an effect handler.

### 6. Measuring a child twice in a `MeasurePolicy`

```kotlin
// ❌ probe-measure, then measure again — the second call throws
val probe = measurable.measure(constraints)
val real = measurable.measure(Constraints.fixedWidth(probe.width))

// ✅ measure once; when a size is needed early, use intrinsics
val placeable = measurable.measure(constraints)
```

Why: layout is single-pass by contract — one measure per child per pass;
pre-measure estimates go through intrinsics or `SubcomposeLayout`.

### 7. Reading per-frame state in composition instead of the draw phase

```kotlin
// ❌ value read while composing — the whole scope recomposes every frame
Badge(Modifier.graphicsLayer(scaleX = pulse, scaleY = pulse))

// ✅ read inside the draw-phase lambda — only the layer updates
Badge(Modifier.graphicsLayer { scaleX = pulse; scaleY = pulse })
```

Why: state reads are tracked per phase — a read during drawing invalidates
just that node's layer, and the recompose scope never runs.

## Progressive disclosure — what to open when

### References (full rules + mechanics)

| Open when the task involves… | File |
|---|---|
| Recomposition, skipping, `remember`, `key(...)`, callbacks as parameters | references/recomposition.md |
| Effect handlers (LaunchedEffect, DisposableEffect, rememberCoroutineScope, SideEffect, produceState, snapshotFlow), effect keys | references/side-effects.md |
| Model classes, collections, or lambdas used as composable parameters; @Stable / @Immutable | references/stability.md |
| Writing state from coroutines or background threads, atomic multi-field updates, explicit snapshots | references/snapshot-state.md |
| Where state lives, stateless/stateful split, ViewModel boundaries, deferred reads | references/state-hoisting.md |
| Modifier chain order, custom modifier factories, phase-aware state reads | references/modifiers-phases.md |
| Custom `Layout` / `MeasurePolicy`, constraints, intrinsics, semantics / testTag | references/layout-semantics.md |

### Examples (canonical Kotlin shapes — copy the form)

| Copy the shape from… | When writing |
|---|---|
| examples/RecompositionAndKeys.kt | Bodies that survive re-execution; `remember(inputs)` derivations; keyed loops; single call sites |
| examples/StableModels.kt | UI model classes, immutable collections, @Stable / @Immutable annotations, memoized lambdas |
| examples/EffectHandlers.kt | Any effect handler usage — subscriptions, suspend loads, callback-launched jobs, state bridges |
| examples/SnapshotStateUsage.kt | State holders outside composables; background-thread writes; snapshot-scoped reads and writes |
| examples/StateHoistingUdf.kt | Stateful screens: state-down/events-up wiring, stateless core + thin stateful wrapper |
| examples/ModifiersLayoutSemantics.kt | Modifier-heavy UI, custom `Layout`s, drawn controls needing semantics and test tags |

Files in this skill

  • SKILL.md9.2 KB
  • examples/EffectHandlers.kt4.5 KB
  • examples/ModifiersLayoutSemantics.kt5.6 KB
  • examples/RecompositionAndKeys.kt4.1 KB
  • examples/SnapshotStateUsage.kt3.8 KB
  • examples/StableModels.kt3.2 KB
  • examples/StateHoistingUdf.kt4 KB
  • references/layout-semantics.md5.8 KB
  • references/modifiers-phases.md4.8 KB
  • references/recomposition.md5.4 KB
  • references/side-effects.md5.9 KB
  • references/snapshot-state.md6.1 KB
  • references/stability.md5.5 KB
  • references/state-hoisting.md5.2 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.