
Posted by Pavlo Stavytskyi, Software Engineer, Meta and Rebecca Franks, Developer Relations Engineer, Google This blog post is written in collaboration with the Meta team. Instagram Direct is one of the core surfaces on Instagram, handling billions of user messages every single day. Over years of iteration, the team squeezed every micro-optimization possible out of the legacy Android View system.
Maintaining and expanding a heavily optimized legacy surface creates significant technical debt and engineering overhead, especially as teams increasingly adopt declarative UI and AI coding assistants. Adopting Jetpack Compose for Instagram Direct went beyond a typical UI modernization. The team built an AI-native UI codebase that is 50% smaller than the original implementation, while achieving a 35% reduction in AI agent execution time, 32% fewer engineer-agent exchanges, and a 33% reduction in token cost. In close partnership with Google, the team adopted Jetpack Compose while maintaining a high performance bar.
Through the performance optimizations, Meta and Google improved Compose not only for Instagram, but for the broader Android developer ecosystem too. Modernizing the codebase at massive scale AI has rapidly become a daily companion for engineers in the industry, and applying it to a large-scale codebase like Instagram already yields real productivity gains. The Instagram Direct team set a more ambitious goal.
Rather than simply pointing AI tools at the existing code, the team redesigned the codebase and its architecture to be AI-native by design, multiplying the impact of AI far beyond what retrofitting alone can deliver. The Instagram Direct team chose Jetpack Compose as a key component for building an AI-native UI architecture.
Its declarative nature ensures code is concise, predictable, and structurally easier for AI models to reason about, with fewer side effects, less implicit state, and clearer component boundaries. The migration to Jetpack Compose required careful planning.
Hundreds of millions of people send messages on Instagram every day, so the migration had to be gradual, smooth, with zero disruption to the experience while the team re-architected the foundation underneath it. To illustrate the scale of the challenge: Individual UI components can render in over 160 distinct state permutations, and a single conversation screen alone handles more than 200 distinct message types.
When migrating a codebase of this size to Compose, it’s tempting to take the easy way out and embed Compose UI components inside the existing View hierarchy. As an incremental step during a gradual migration, that’s perfectly valid.
Over the long run, though, integrating Compose inside a View-based codebase poses a challenge. AI tools often take the path of least resistance.
If you mix declarative and imperative UI code, AI is likely to blend them incorrectly, introducing subtle bugs, tech debt and performance regressions. Building an AI-native UI architecture At the scale of Instagram, a degree of architectural abstraction is unavoidable, and it is what keeps the app maintainable as it grows. Consider a common pattern, where every RecyclerViewitem type is modeled as a descendant of a custom RecyclerView base class that exposes usual lifecycle hooks such as onBind.
Example 1 class ChatItem( val features: FeatureFlagProvider): RecyclerViewItem<ComposeViewHolder, ChatUiState> { // Imperative context: // AI could often take the path of least resistance and generate a mutable // state here, dispatched outside the ChatUiState. This class survives // re-bindings and is shared across multiple items, ultimately leading to // unexpected, hard-to-reproduce bugs.
} } } In the snippet above, two problems creep in. First, the isPinnedChatsEnabled flag is read in imperative code and then captured inside a Compose lambda, a subtle coupling across paradigms.
Second, isPinned lives as a mutable field on the item itself rather than in ChatUiState, so it survives RecyclerView re-binding and recycling across rows, leaking and producing bugs that are painful to reproduce. Even when the code is cleaned up by giving the item a dedicated @Composable function, the same problems remain.
} } This is deliberately a simple example, but it illustrates a broader issueโ the fewer boundaries AI is given, the lower the quality of the code it produces over time. Guardrails and skills help, but they are not enough on their own, because when AI hits friction it will often route around them to unblock itself.
To make the codebase AI-friendly, it needs to follow two practical rules: Minimize dependency on custom context. The more bespoke, codebase-specific knowledge an AI agent needs to make a correct change, the lower the quality of its output.
Discover more from ChuckysCarnage
Subscribe to get the latest posts sent to your email.
