Jetpack Compose Performance: Zero-Jank UI Guide
Learn how to reduce jank in Jetpack Compose with profiling, recomposition fixes, LazyColumn tips, and startup optimizations.
Jetpack Compose is the future of Android UI — but it’s also very easy to build screens that look great and still feel laggy. If your app has stutters, skipped frames, or slow startup, users notice instantly.
In this guide, you’ll learn the highest-impact techniques for building a zero-jank Compose UI, keeping scrolling buttery smooth, and making your app launch faster.
What “jank” actually means in Android
Jank happens when the UI thread can’t produce frames fast enough. Most modern Android devices target 60fps, meaning you have ~16ms per frame. At 120Hz, it’s even stricter.
If Compose does too much work inside recomposition, layout, or drawing — frames drop.
1. Fix the #1 Compose issue: unnecessary recompositions
Recomposition is normal. Too much recomposition is the problem.
Watch out for these recomposition triggers
- Passing unstable objects into composables
- Recreating lambdas each recomposition
- Building lists in the UI layer repeatedly
- State stored too high in the UI tree
Practical fixes
- ✅ Use
remember { }for expensive objects - ✅ Prefer immutable models (data class, persistent collections)
- ✅ Use
derivedStateOffor computed values - ✅ Keep state as low as possible (closer to the component that needs it)
Example patterns:
- “Screen state” in ViewModel
- UI-only ephemeral state in composables (
rememberSaveable) - Derived values computed lazily, not on every recomposition
2. LazyColumn performance: stop rebuilding items
If you’re using LazyColumn, the most common performance issue is unstable item identity.
Always use stable keys
Use:
- a database id
- a UUID
- a stable unique string
When keys aren’t stable, Compose may recompose/recreate items more than needed.
Avoid heavy UI work inside each row
Bad patterns:
- formatting dates repeatedly in composables
- decoding images synchronously
- computing text styling logic repeatedly
Move these to:
- ViewModel
- domain layer
- memoized values
3. Optimize images the right way
Images kill performance more than almost anything else.
Recommendations
- Use proper caching (Coil is standard)
- Don’t load full-size images when a thumbnail is enough
- Pre-size images when possible
- Avoid decoding on the main thread
If your scrolling list contains image thumbnails, you should treat image loading as part of your “core performance budget”.
4. Reduce startup time (the silent killer)
Even if your UI is smooth, a slow launch feels “heavy”.
High-impact startup optimizations
- Minimize work in
Application.onCreate() - Defer initialization (analytics, remote config, loggers)
- Use baseline profiles for critical startup paths
- Avoid blocking the main thread with I/O
Rule: show UI fast, load data progressively.
5. The profiling stack you should use
If you’re serious about performance, you need the right tools.
Recommended toolchain
- Android Studio Profiler (CPU + Memory)
- Layout Inspector for Compose
- Macrobenchmark (startup/scroll performance)
- JankStats (frame drops in the real app)
Measure → change → re-measure. Performance is engineering, not guessing.
Best practices checklist
- ✅ Stable keys in Lazy lists
- ✅ Avoid expensive work in composables
- ✅ Immutable models / stable state inputs
- ✅ Defer non-critical startup work
- ✅ Profile scroll + startup with benchmarks
- ✅ Reduce recompositions with correct state placement
FAQ
Is Jetpack Compose slower than XML? Not inherently. Compose can be extremely fast — but it’s easier to accidentally create unnecessary recompositions if state handling isn’t clean.
What’s the fastest way to improve scrolling performance? Start with:
- stable list keys
- move expensive computations out of composables
- optimize image loading and caching
If you have a Compose app that stutters, drops frames, or feels slow, performance audits can often fix 80% of issues in days. Check our Performance & Stability Services