
Key takeaways
• Jetpack Compose performance is still a real production risk in 2026. Misuse of state, lambdas, and unstable types still ship apps at ~2 FPS on flagship hardware — 450 ms frame times against a 16 ms budget.
• Vkompose is the most aggressive line of defense. The VK.com open-source toolkit ships an IntelliJ plugin, a Gradle plugin, and Detekt rules that highlight, log, and block recomposition issues at compile time — not just at runtime.
• Strong Skipping Mode is on by default since Kotlin 2.0.20. It skips composables whose parameters are reference-equal even when technically unstable, so verify it is active before you hand-write @Stable annotations.
• Layered defense beats single tools. Stack Strong Skipping + Vkompose + Detekt + Layout Inspector + Baseline Profiles + Macrobenchmark — each one catches a different class of regression and CI/CD ties them together.
• Real teams ship faster after migration. Twitter cut UI boilerplate 10×, Reddit shipped Recap on Compose, R8’s optimize profile cuts ~30% from cold start. Adoption pays back if the performance discipline is in place.
Why Fora Soft wrote this playbook
Fora Soft has built Android apps since 2005 — 250+ projects and 50 in-house engineers, a large share of them on mobile, video, and real-time products, many on Jetpack Compose for the last three years. Plenty were video-heavy apps where the same rigor carries into the video engineering layer underneath. We’ve hit every recomposition trap on the list below, fixed them in production, and codified the discipline into our CI/CD checks.
Vkompose is not our project — it is open source from VK.com’s Android team — but we use it on most Compose-heavy engagements because no other toolkit gives the same combination of compile-time blocking, IDE highlighting, and runtime recomposition logging in one bundle. This article is the practical playbook we hand to our Android engineers when they join a Compose project, paired with the broader performance toolkit (Strong Skipping, Modifier.Node, Baseline Profiles, R8 optimization) that makes Vkompose pay off.
If you are scoping or auditing an Android product on Compose — whether the buyer is a CTO worrying about jank, a product owner negotiating a launch deadline, or a founder picking a partner to ship the v1 — this guide is the same one we would walk you through on a call. We use Agent Engineering on every project to compress the calendar and the cost; that’s why our quotes typically land below the industry baseline.
Compose app feeling janky?
Get a 30-minute Compose performance review with our Android leads — we will scan the codebase for the top recomposition traps and give you a concrete remediation plan.
Why Compose performance still hurts in 2026
Jetpack Compose performance still hurts because the fast path is opt-in: the runtime only skips work when your state and parameters stay stable, and most teams don’t keep them that way. Jetpack Compose is the default Android UI toolkit and the language of every modern Android team — Google’s January 2024 release alone landed roughly a 20% scroll-FPS bump and a 12% startup improvement. The platform keeps getting faster. The applications keep getting slower.
The reason is that Compose’s declarative model trades raw control for ergonomic state management — and ergonomic state management is easy to misuse. Documented production cases have shown Compose feeds running at ~2 FPS (450 ms per frame) on flagship devices because of one unstable List parameter that triggered 47 recompositions per scroll gesture. None of that shows up in code review; it only surfaces under Layout Inspector or under user complaints.
The fix is not heroic optimization. It is a layered defense: Strong Skipping Mode, Vkompose for compile-time and runtime checks, Detekt rules for code-smell coverage, Layout Inspector for spot debugging, Baseline Profiles for AOT, and R8 Full mode for everything else. Each tool catches a different class of regression. Stacked together, they take a production Android team from “hope it’s smooth” to “CI/CD blocks janky merges.”

Figure 1. Where each frame is skipped or re-run. Unstable params force a re-run every time — that’s the jank.
The seven recomposition traps every Compose team falls into
If you are auditing a Compose codebase, scan for these seven patterns first — they cover roughly 90% of our remediation work.
1. Unstable types as parameters. Collections (List, Set, Map) are always unstable to the Compose compiler. Data classes are unstable unless every property is too. Cross-module models are unstable if the producing module isn’t built with Compose. Annotate with @Stable/@Immutable, switch to ImmutableList from Kotlinx-Immutable, or use a stability config file.
2. Lambdas captured in unstable scope. Inline lambdas allocated each recomposition recompose every child that takes them. Pass method references (::onClick) or use remember { { … } } for stable identity.
3. Recomposition scope too wide. Reading state at the wrong level invalidates the whole subtree. Push state reads down to the smallest leaf composable that needs them; pass lambdas instead of values when a leaf needs to mutate state.
4. Missing keys in LazyColumn/LazyRow. Without stable keys Compose treats every reorder as a new item, discarding remembered state and re-running. Always pass key = { it.id }.
5. Side effects in the composable body. DB queries, file I/O, JSON parsing inside a composable run every recomposition. Wrap in remember { … } or LaunchedEffect.
6. derivedStateOf misuse. Using it where a plain value would do adds overhead; not using it where you should triggers cascading recompositions. Reach for it when a derived value depends on multiple state reads but doesn’t need to recompose every time the source changes.
7. Modifier.composed instead of Modifier.Node. The composed factory rebuilds modifiers on every recomposition; the Node API survives them. Migrating performance-critical custom modifiers gives a 3×–10× speedup on benchmark micro-tests.
Reach for an external code audit when: the team can list the symptoms (jank, ANR, scroll stutter) but not the cause, or the codebase predates Strong Skipping (Compose Compiler < 1.5.4). Our code-auditing playbook explains the diagnostic flow we run.
Read the compiler reports before you write fixes
The Compose compiler can emit two text reports that tell you exactly which classes are unstable and which composables are skippable. Turn them on first; everything else is hypothesis.
tasks.withType<KotlinCompile>().configureEach {
compilerOptions {
freeCompilerArgs.addAll(
"-P", "plugin:androidx.compose.compiler.plugins.kotlin:" +
"reportsDestination=${'$'}{buildDir}/compose_metrics",
"-P", "plugin:androidx.compose.compiler.plugins.kotlin:" +
"metricsDestination=${'$'}{buildDir}/compose_metrics"
)
}
}
After the next build, compose_metrics/<module>-classes.txt lists every class as Stable, Immutable, or Unstable; composables.txt shows which functions are Restartable and Skippable. The first audit pass is just reading these and prioritising the screens whose top composables fail to skip.
Vkompose — what each plugin actually does
Vkompose is a bundle of three independently-installable Compose-performance tools maintained by VK.com’s Android engineering team. The bundle solves the same family of problems Layout Inspector does — but with stronger blocking, more flexible logging, and CI/CD-friendly outputs.
1. The IntelliJ IDEA / Android Studio plugin
Highlights composables that are likely to recompose unnecessarily — missing @Stable annotations, unstable parameters, miss-marked composable functions. The IDE colours warn at the level of the function declaration, so junior engineers see the issue while writing the code, not a sprint later.
2. The Gradle plugin (the strict one)
Runs at compile time and reports every composable that won’t skip. Wire its Detekt rule (NonSkippableComposable / ParamsComparedByRef) into CI, or switch on strong-skipping fail-fast, and an unskippable composable fails the build — the difference between a tip and an enforcement. The plugin also ships RecomposeLogger, an opt-in runtime utility that prints exactly which parameter changed when a function recomposed.
// build.gradle.kts (module)
plugins {
id("com.vk.vkompose") version "0.7.2" // latest, Dec 2025 (tracks Kotlin 2.2.21)
}
vkompose {
skippabilityCheck = true // report composables that won't skip
recompose {
isHighlighterEnabled = true // colored border around recomposing nodes
isLoggerEnabled = true // logs which argument changed on recomposition
}
testTag { isApplierEnabled = true } // auto-generate a testTag on default Modifier
}
3. The Detekt rules
Static-analysis rules that fit into your existing Detekt pipeline. They catch missing parameter types, unstable parameter shapes, and the small set of code-smell patterns that Vkompose’s compiler-level checks deliberately skip.
Vkompose vs. Layout Inspector vs. Rebugger vs. Detekt rules
There are at least four ways to track Compose performance issues. They are not interchangeable.
| Tool | Catches | Blocks build? | Best for |
|---|---|---|---|
| Layout Inspector | Recompose / skip counts, hierarchy | No | Spot debugging on emulator only |
| Rebugger | Per-component recomposition reasons | No | Lightweight ad-hoc tracing |
| Detekt rules for Compose | Code-smell patterns (Modifier reuse, parameter naming) | Yes (Detekt threshold) | Style consistency in CI |
| Vkompose | Stability mistakes, unskippable composables, runtime recomposition causes | Yes (Gradle plugin) | Production-grade enforcement + live debugging |
Reach for Vkompose when: you have a Compose codebase larger than ~50 screens, a CI/CD pipeline that needs to reject performance regressions, and an Android team big enough that runtime debugging by hand does not scale. For a 1–2-person hobby app, Layout Inspector + the compiler reports are usually enough.

Figure 2. Four tools, four jobs. Only Vkompose and the Detekt rule fail a regression in CI.
Strong Skipping Mode — verify it’s on
Compose Compiler 1.5.4 introduced Strong Skipping Mode, and it has been on by default since Kotlin 2.0.20. So in 2026 the job is usually to confirm it’s active, not to switch it on. With it enabled, a composable whose unstable parameter is reference-equal (===) to last time is skipped instead of re-running, and lambdas with unstable captures are auto-memoized.
// build.gradle.kts (default on Kotlin 2.0.20+; set this only on OLDER Kotlin)
composeCompiler {
enableStrongSkippingMode = true
}
// opt a hot composable out with @NonSkippableComposable;
// keep a specific lambda un-memoized with @DontMemoize
On the client codebases we’ve measured, confirming Strong Skipping is active clears a large share of needless recompositions with zero code changes — the cheapest mitigation in the toolkit. Check it before you spend a day hand-writing @Stable annotations; many of them are no longer necessary.
Need a Compose performance audit?
Our Android leads will run Vkompose, Layout Inspector, Macrobenchmark, and the compiler reports against your codebase — you get a punch list with effort estimates in 7–10 days.
Layout Inspector — for the bug you can already reproduce
Once a screen feels janky on a real interaction, Layout Inspector is the fastest tool to find the offending composable. Open Tools → Layout Inspector, attach to your debug build, and enable “Show Recomposition Counts” in the component-tree options.
Two columns appear on every node: Recompose (how many times the function ran) and Skip (how many times Compose chose to skip). A high Recompose-to-Skip ratio — especially under interactive load — is the smoking gun. Layout Inspector ships in Android Studio so it is the default starting point; Vkompose’s runtime highlighter complements it on physical devices.
Baseline Profiles and Macrobenchmark — how we prove it
Eyeballing FPS does not survive a CI pipeline. The combination that does is Baseline Profiles + Macrobenchmark: ship pre-compiled hot paths to cut cold-start by ~30%, then measure P95 frame latency in a reproducible test.
// :baseline-profile module
@RunWith(AndroidJUnit4::class)
class FeedScrollBenchmark {
@get:Rule val rule = MacrobenchmarkRule()
@Test fun feedScrollFrames() = rule.measureRepeated(
packageName = "com.example.app",
metrics = listOf(FrameTimingMetric()),
iterations = 10,
startupMode = StartupMode.COLD
) {
pressHome()
startActivityAndWait()
device.findObject(By.res("feed")).fling(Direction.DOWN)
}
}
Run it nightly. Fail the pipeline if P95 frame time creeps over 16 ms. Pair it with a Baseline Profile generator that records a critical user flow; the AOT-compiled output is what gives the 30% startup uplift Google quotes.
Reach for Baseline Profiles when: the complaint is slow cold start, not mid-scroll jank — they cut first-run time by roughly 30% with zero source changes.
Migrate Modifier.composed to Modifier.Node
Modifier.composed { … } rebuilds a fresh modifier instance every time the parent recomposes, allocating along the way. The Modifier.Node API survives recompositions, holds state directly, and benchmarks at 3×–10× the speed.
// Old — avoid
fun Modifier.brandRipple() = composed {
val state = remember { mutableStateOf(0) }
drawWithContent { /* ... */ }
}
// New — prefer
private class BrandRippleNode : Modifier.Node() {
var state = mutableStateOf(0)
// hooks here survive recompositions
}
fun Modifier.brandRipple(): Modifier =
this then BrandRippleElement
Audit every custom modifier in the codebase. Migrate the ones in lists, in animations, and in complex layouts first — that’s where the allocations stack up.
R8 Full mode + a stability config file
Two configuration changes routinely move metrics by 20–30% with zero source changes.
1. R8 Full mode. Switch proguard-android.txt to proguard-android-optimize.txt and enable android.enableR8.fullMode=true. On the apps we’ve shipped it clears a measurable chunk of startup time and jank with no source changes; the exact number is app-specific, so we quote it only after a before/after Macrobenchmark run.
2. A stability configuration file. Annotate third-party types you can’t change as stable in a single configuration file (stability_config.conf). Compose treats them as @Stable at compile time without monkey-patching the dependency.
CI/CD — turning the toolkit into a guard rail
A toolkit only matters if a build fails when it should. Wire Vkompose, Detekt, and Macrobenchmark into your pipeline so every PR proves it does not regress.
// .github/workflows/compose-perf.yml
name: Compose performance gate
on: [pull_request]
jobs:
perf:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: ./gradlew detekt
- run: ./gradlew :app:assembleRelease # Vkompose Gradle plugin
- run: ./gradlew :baseline-profile:macrobenchmark
- run: ./tools/check-frame-budget.sh # fail if P95 > 16 ms
Reach for a stability config file when: the unstable types live in third-party libraries you can’t annotate — one config file marks them @Stable at compile time without forking the dependency.
Production case studies — what well-run Compose teams ship
Twitter reduced UI component boilerplate by ~10× after their full Compose migration, alongside faster bug fixing and more flexible theming for A/B testing. Reddit shipped “Recap” on Compose, leaning on its animation engine instead of nested ConstraintLayout. Square / Cash App have publicly committed to Compose for new surfaces and report comparable engineering velocity gains internally. Google’s January 2024 release notes alone documented ~20% faster scroll FPS and ~12% faster startup — gains every team inherits for free with a Compose Compiler upgrade.
Compose Multiplatform — where Vkompose stops
Vkompose ships as Android Gradle plugin + Detekt rules and is Android-only. Teams using Compose Multiplatform for desktop, iOS, or Web will not get Vkompose enforcement on those targets. Plan a separate profiling strategy: Macrobenchmark/Compose Multiplatform compiler reports on iOS, Compose Web Lighthouse, desktop benchmark rig via JFR.
Cost of fixing Compose performance — what to expect
When Fora Soft picks up an audit-and-fix engagement on a Compose product, the typical scope and timeline below is what we quote.
| Engagement | What is included | Indicative range | Calendar |
|---|---|---|---|
| Compose performance audit | Compiler reports, Vkompose run, Layout Inspector traces, prioritized punch list | $6K–$12K | 7–10 days |
| Tier-1 fixes | Strong Skipping, key parameters, lambda capture cleanup, R8 Full mode | $10K–$25K | 2–3 weeks |
| Tier-2 refactor | State-shape redesign, derivedStateOf cleanup, Modifier.Node migration | $25K–$60K | 4–8 weeks |
| CI/CD hardening | Vkompose Gradle plugin, Detekt rules, Macrobenchmark in CI, perf budgets | $8K–$15K | 1–2 weeks |

Figure 3. Indicative ranges for a Compose performance engagement. Solid bar is the floor, lighter is the ceiling.
A decision framework — pick a Compose performance plan in five questions
1. What Compose Compiler version are you on? Below Kotlin 2.0.20 → upgrade first; that alone turns Strong Skipping on by default. On 2.0.20+ → it’s already on, so go straight to the full toolkit.
2. How many screens? Below ~20 → compiler reports + Layout Inspector are enough. Above 50 → install Vkompose Gradle plugin and ratchet CI checks on.
3. What is the user-visible symptom? Cold start → Baseline Profiles + R8 Full. Scroll jank → Vkompose + state hygiene. Animation stutter → Modifier.Node + GPU profiling.
4. Cross-platform footprint? Android-only → full Vkompose stack. Compose Multiplatform → layered approach with platform-specific profilers per target.
5. Where is performance enforced today? Nowhere → start with Detekt + Vkompose Gradle in CI before doing source fixes. Already in CI → tighten budgets and add Macrobenchmark.

Figure 4. Cheapest wins first: verify strong skipping and ship a Baseline Profile before you reach for heavy tooling.
Pitfalls we have watched Compose teams fall into
1. Annotating everything as @Stable. Lying to the compiler hides recompositions instead of fixing them. The crash arrives later and it is harder to debug.
2. Skipping Strong Skipping “until we’re ready”. It is production-ready and free. Turning it on later means you will write annotations you didn’t need to.
3. Treating Layout Inspector as a CI tool. It is interactive only. Wire Macrobenchmark and Vkompose Gradle into the pipeline; reserve Layout Inspector for spot debugging.
4. Refactoring state without measuring first. Speculative state refactors often regress something else. Always pull a Macrobenchmark baseline before, after, and a week later.
5. Ignoring R8 Full mode. ~30% startup gains for free are not optional in 2026. Verify the obfuscation rules don’t break your reflection-heavy dependencies and ship it.
KPIs — what to measure and what to budget
Quality KPIs. P95 frame time < 16 ms during scroll, time-to-first-frame < 500 ms cold, ANR rate < 0.1% of sessions, recompose:skip ratio < 1.5 on your hot screens.
Business KPIs. Crash-free sessions over 99.5%, install-to-day-1 retention movement after each performance ship, Play Store Vitals “bad behaviours” under the bad-behaviour threshold (currently 0.47% slow frames).
Reliability KPIs. CI gate uptime, Macrobenchmark variance under 5% across runs, time-to-bisect a regression under one hour. Without a stable benchmark you cannot defend a performance budget.
When NOT to invest in heavy Compose tooling
A solo developer on a 5-screen MVP, a hobby project, or a feature flag the company is days away from killing — none of them earn the cost of standing up Vkompose, Detekt, and Macrobenchmark in CI. For those, ship Strong Skipping plus the compiler reports and move on.
Investment becomes obvious when the product crosses ~50 screens, the team grows past ~5 Android engineers, or a VIP customer reports jank. Past those thresholds, Vkompose+CI pays for itself in a sprint.
Want a punch list, not theory?
Send your repo — we will run Vkompose, Detekt, and Macrobenchmark and return a prioritized fix plan with effort estimates inside ten days.
FAQ
Is Vkompose maintained by Fora Soft?
No. Vkompose is open-source from VK.com’s Android team. Fora Soft uses it on Compose-heavy engagements because it is the most aggressive open-source toolkit for blocking unoptimized Compose code at compile time.
Should we enable Strong Skipping Mode now or wait?
Now. It is production-ready in Compose Compiler 2.0+, fixes about 30% of recomposition issues without code changes, and is the cheapest mitigation in the toolkit. Turn it on before any annotation work.
How does Vkompose compare to Rebugger?
Rebugger is a runtime logging library — lightweight and good for ad-hoc tracing. Vkompose is a full bundle — IDE plugin, Gradle plugin, Detekt rules — that can fail builds when unoptimized code is detected. They are complementary; we use both on large codebases.
Does Vkompose work with Compose Multiplatform?
No. Vkompose is Android-only because it integrates with the Android Gradle plugin. Cross-platform Compose teams need to layer in Macrobenchmark, Compose Multiplatform compiler metrics, and platform-specific profilers per target.
What is the cheapest single change for Compose startup time?
Switching to proguard-android-optimize.txt with R8 Full mode and shipping a Baseline Profile generated from your top user flow. Together they consistently buy 25–35% off cold start.
How much does a Compose performance audit cost at Fora Soft?
$6K–$12K for a full audit (compiler reports, Vkompose run, Layout Inspector traces, Macrobenchmark baseline, prioritized punch list with effort estimates). Most clients then commission Tier-1 fixes which run $10K–$25K.
Is Compose ready for large-scale production teams?
Yes. Twitter, Reddit, and Square run Compose at scale. Tooling (Vkompose, Detekt, Layout Inspector, Macrobenchmark, Baseline Profiles) is mature enough for 100+ engineer teams. The bar today is performance discipline, not framework readiness.
Does Fora Soft work on Android-only or full mobile stacks?
Both. We ship Android, iOS, KMP, and Compose Multiplatform projects. See our 2024 mobile development highlights for representative work.
What to Read Next
Android performance
10 ways to optimize Android apps for smooth video streaming
Adjacent performance disciplines — codecs, threading, memory — that pair with Compose hygiene.
Audit playbook
What is code auditing and how to conduct it
The audit framework we use before any optimization sprint — not just for Compose.
Android engineering
Android WebRTC screen sharing — the complete implementation guide
A worked example of an Android feature whose performance budget is unforgiving.
Cost
Mobile app development costs — the 2025 guide
How an Android engagement is priced — including audit-and-fix scopes like the one above.
Ready to make Compose fast and keep it fast?
Jetpack Compose is fast enough for production — when teams keep state shape clean, lambdas stable, and recomposition scope tight. The toolkit to enforce that discipline exists today: Strong Skipping, Vkompose, Detekt, Layout Inspector, Modifier.Node, Baseline Profiles, R8 Full. Stack them, wire them into CI, and your Android product stops trading performance for declarative ergonomics.
If you want the discipline applied to your codebase — or a punch list of what to fix first — book a 30-minute call. We will scan, score, and quote a fix plan with effort estimates in ten days, not ten weeks.
Talk to our Android engineering leads
Book a 30-minute call. We will walk Vkompose, Strong Skipping, R8 Full, and the rest of the playbook against your codebase.

