Loading…
How We Decomposed Tinder’s Monolith
TinderTinder
Summary
Tinder faced significant agility and build performance challenges caused by an iOS codebase monolith containing over 1,000 files and 150,000 lines of code. Manual extraction efforts risked creating massive, unmanageable pull requests that would require constant rebasing against the main branch. To systematically decompose the target into Swift sub-modules, the team mapped declarations and references via the Swift compiler into a directed graph. They iteratively extracted leaf nodes with an in-degree of zero across sequential phases and automated common code adjustments, including module dependencies, imports, access control levels, and dependency injection. The automated decomposition completed in under six months with zero P0 incidents, reducing monolith build times by 78% and disallowing future additions to the monolith target.
Context
The Tinder iOS application contained a core monolith target with over 1,000 files and more than 150,000 lines of code accumulated over ten years. This entangled structure caused slow build times, lacked clear code ownership and tests, and hindered organizational agility. Additionally, deep build graphs lengthened compilation critical paths by restricting parallel compilation across machine cores.
Approach / What changed
The team modularized the codebase by extracting code from the main monolith target into Swift sub-targets. Using Swift compiler declaration and reference data, they modeled the monolith as a directed graph and removed leaf nodes with an in-degree of zero in sequential phases. To avoid an estimated twelve-year manual effort, they automated file extractions and necessary downstream adjustments—such as updating module dependencies, imports, access control levels, and dependency injection—which allowed pull requests to be re-healed automatically during rebases.
Takeaways
- Extracting files starting with leaf nodes (in-degree of 0) prevents sprawling pull requests that occur when pulling deeply intertwined dependencies out of a monolith.
- Each moved file required edits across an average of 15 other files, predominantly consisting of module dependency updates, import adjustments, access control changes, and dependency injection fixes.
- Automating code extraction and compilation fixes reduced the estimated project timeline from twelve years to under six months while cutting monolith build times by 78% with zero P0 incidents.
Related reading
Grab ·
How Grab is Blazing Through the Superapp Bazel Migration
Grab's mobile superapp scaled past 2.5 million lines of code across both Android and iOS, leading to unsustainable local and CI build times under Gradle and Xcode. To address these bottlenecks, the engineering team analyzed their dependency trees and introduced an internal tool to calculate and optimize the build critical path. They also deployed a Kubernetes-autoscaled remote build system using Mainframer for Android and implemented Test Impact Analysis to run only affected tests in pre-merge validation. While dependency decoupling yielded modest 7% to 10% gains and iOS remote builds proved unscalable on Apple hardware, Android remote builds reduced local compile times by up to 50%, and targeted test execution reduced pre-merge pipeline durations by more than 30%.
Sergii GrechukhaGrab ·
App Modularisation at Scale
Grab transitioned its monolithic mobile application into a modular architecture to resolve increasing code conflicts, slow releases, and difficult team collaboration. The team decomposed the single module by establishing base infrastructure modules, shared UI and utility libraries, discrete feature modules, and bridge kit modules for inter-module communication. Dependency injection using Dagger ties these components together in the main app module while preventing feature modules from directly depending on one another. The architecture spans over 1,000 modules across the app, with more than 200 modules in the Grab Financial Group payments domain where over 95% of modules build in under 15 seconds. This approach accelerated Gradle CI and local builds through parallel compilation and caching, though it increased Gradle sync times, IDE memory usage, and configuration maintenance overhead.