Skip to content

Violations

A violation occurs when a composable’s recomposition rate exceeds its budget, adjusted for the current interaction state.

The violation check runs on every onComposition call:

effectiveBudget = baseBudget * interactionMultiplier
if (currentRate > effectiveBudget) {
// violation
}

For example, a SCREEN composable (budget 3/s) during IDLE (multiplier 1.0x) violates at any rate above 3/s. The same composable during SCROLLING (multiplier 2.0x) would need to exceed 6/s.

A typical violation in logcat looks like:

W/Rebound: [VIOLATION] ProfileHeader — 11 recomp/s (budget: 5, class: LEAF)
-> params: avatarUrl=DIFFERENT, displayName=DIFFERENT
-> forced: 0 | param-driven: 11 | interaction: IDLE

Breaking this down:

FieldMeaning
ProfileHeaderThe composable’s resolved name
11 recomp/sCurrent recomposition rate (rolling 1-second window)
budget: 5Maximum acceptable rate for this budget class
class: LEAFThe budget class assigned by the compiler
avatarUrl=DIFFERENTThe avatarUrl parameter changed between compositions
displayName=DIFFERENTThe displayName parameter also changed
forced: 0Zero recompositions were forced by a parent
param-driven: 11All 11 recompositions were driven by parameter changes
interaction: IDLEThe app was idle (no scrolling, animation, or input)

Each parameter is reported with one of four states decoded from the Compose compiler’s $changed bitmask:

  • DIFFERENT — the parameter value changed since the last composition
  • SAME — the parameter value is the same as the last composition
  • STATIC — the parameter is known to be constant at compile time
  • UNCERTAIN — the compiler could not determine the parameter’s stability
  • Forced — the parent composable was invalidated and all its children re-execute, regardless of whether their parameters changed. The fix is usually in the parent, not the child.
  • Param-driven — a specific parameter changed, triggering this composable to recompose. The fix is to stabilize the parameter or debounce the state that produces it.

Violations are throttled in two ways:

  1. Per-composable throttle — maximum one violation per composable per 5 seconds. This prevents logcat flooding when a composable is continuously over budget.
  2. Composition logging throttle — when logCompositions = true, non-violation composition events are logged at most once per composable per second.

Some situations produce violations that are not actual performance problems:

During the first few seconds of app launch, many composables compose rapidly as the UI tree is built. These initial compositions can temporarily exceed budgets. Rebound’s 1-second rolling window means these violations are short-lived and stop once the UI settles.

Screen transitions may briefly push SCREEN composables above their budget. If the violation clears within a second, it is likely a transition — check the Timeline tab to confirm the spike is transient.

Instrumented tests that rapidly drive recompositions via mutableStateOf may produce violations. This is expected — the tests are deliberately stressing the composable.

When a violation is a false positive, you have two options:

  1. Override the budget class with @ReboundBudget to assign a more appropriate class
  2. Ignore it — transient violations during transitions are not actionable

If you see UNKNOWN budget class violations frequently, add @ReboundBudget annotations to classify those composables correctly.