How to Audit a Mobile App Codebase You Inherited: A Step-by-Step Checklist
You have just taken over a mobile app. Maybe the original development team left, the agency contract ended, or your company acquired the product. Whatever the reason, you are now responsible for a codebase you did not write, with decisions you did not make, targeting deadlines you did not set. The first thing you need is a structured audit.
A codebase audit is not about judging the previous team. It is about building an accurate picture of what you are working with so you can make informed decisions about what to fix first, what risks are lurking, and how much effort future changes will require. Here is the checklist we use when inheriting a mobile app codebase.
Step 1: Dependency Inventory
Start with the dependency manifest. On Android, this is your build.gradle or build.gradle.kts files. On iOS, check Podfile, Package.swift, or Cartfile depending on which package manager is in use. Some older projects still use manually dragged-in frameworks with no package manager at all, which is itself a finding worth noting.
For each dependency, record three things: the current version in the project, the latest available version, and whether any known security vulnerabilities exist. On Android, run ./gradlew dependencyUpdates using the Gradle Versions Plugin. On iOS, pod outdated gives you a quick view if the project uses CocoaPods. For Swift Package Manager, Xcode shows available updates in the package dependency inspector.
Security vulnerabilities are the highest priority finding. Run npm audit or yarn audit if the project includes a React Native or JavaScript layer. For native dependencies, cross-reference with the GitHub Advisory Database or use tools like Snyk or Dependabot. A dependency with a known CVE that handles user input, authentication, or network traffic is a critical finding that should be escalated immediately.
Pay special attention to dependencies that are more than two major versions behind. A library at version 3.x when the current release is 6.x often means the migration path is non-trivial and may involve breaking API changes across multiple upgrade steps.
Step 2: Architecture Assessment
Before you can maintain the app effectively, you need to understand how it is structured. Look at the project's directory layout and identify the architectural pattern in use. Is it MVC, MVVM, MVI, VIPER, Clean Architecture, or something ad hoc? Many inherited codebases start with one pattern and drift into another over time, resulting in a hybrid that follows no clear convention.
Check for separation of concerns. Are network calls happening directly in view controllers or activities? Is business logic mixed into the UI layer? Are there distinct data, domain, and presentation layers, or is everything in one place?
On Android, check whether the project uses Jetpack components (ViewModel, LiveData, Room, Navigation) or older patterns (AsyncTask, Loader, raw SQLite). On iOS, look for whether the project uses Combine or async/await versus older callback-based patterns, and whether it has adopted SwiftUI or remains entirely UIKit.
Document the navigation structure. Is there a navigation graph (Android) or coordinator pattern (iOS), or does each screen push and pop directly? Spaghetti navigation is one of the biggest sources of bugs in inherited apps.
Step 3: Test Coverage Analysis
Run the existing test suite and measure what you actually have. On Android, ./gradlew testDebugUnitTest and ./gradlew connectedDebugAndroidTest will execute unit and instrumentation tests respectively. On iOS, xcodebuild test with the appropriate scheme runs the test target. Enable code coverage reporting to get a percentage.
The raw coverage number matters less than where the coverage exists. An app with 40% overall coverage but solid tests around the authentication flow, payment processing, and data persistence layer is in much better shape than an app with 70% coverage that only tests utility functions and data models.
Check whether the tests actually run. In many inherited codebases, the test target has been broken for months. Tests that do not compile or that have been skipped with annotations like @Ignore or disabled are effectively nonexistent. Count only tests that execute and pass.
Step 4: CI/CD Pipeline Review
Look for CI/CD configuration files: .github/workflows, Jenkinsfile, .gitlab-ci.yml, bitrise.yml, fastlane/Fastfile, or circle.yml. If none exist, that is a significant finding. It means builds and releases have been done manually from a developer's local machine, which introduces reproducibility and security risks.
If a pipeline exists, check what it actually does. Does it run tests? Does it run lint checks? Does it build both debug and release variants? Does it handle code signing and provisioning profile management? Does it deploy to TestFlight or Google Play internal tracks, or does someone still upload builds manually?
Verify that the pipeline actually works by triggering a build. Stale CI configurations that reference old Xcode versions, expired signing certificates, or removed environment variables are common in inherited projects.
Step 5: Crash Analytics and Production Health
Access the crash reporting dashboard. Firebase Crashlytics is the most common tool, but the app may use Sentry, Bugsnag, or Datadog. If there is no crash reporting at all, that is a critical gap you need to close before doing anything else.
Review the crash-free rate over the past 90 days. A healthy app maintains a crash-free user rate above 99.5% on iOS and 99% on Android. Look at the top crash clusters. Are they trending up or down? Are there crashes that have been present for months without being addressed?
Check whether crashes correlate with specific OS versions, device models, or app versions. A spike in crashes on Android 14 that the previous team never investigated tells you exactly where to focus early maintenance effort.
Step 6: Code Style and Lint Configuration
Check for linter configuration. On Android, look for lint.xml, .editorconfig, and ktlint or detekt configuration. On iOS, look for .swiftlint.yml. If linting is configured, run it and count the warnings. A project with 400+ lint warnings has been ignoring code quality enforcement for some time.
Check whether the project uses any code formatting tool (ktfmt, swift-format, or Prettier for React Native). Inconsistent formatting across files often indicates multiple developers with different editor configurations working without a shared standard.
Step 7: Build Configuration Audit
Review the build configurations and signing setup. On Android, check the build.gradle for signing configs, build types, product flavors, and ProGuard/R8 rules. On iOS, examine the Xcode project for build configurations, provisioning profiles, entitlements, and Info.plist values.
Verify that debug and release builds are properly separated. Check that API keys, backend URLs, and feature flags differ between environments. Hardcoded production API keys in debug builds or debug logging enabled in release builds are common findings that represent both security and performance risks.
Step 8: Documentation Gaps
Check for a README with build instructions. Can a new developer clone the repo and build the app on the first try? Missing environment variables, undocumented dependencies on local tools, or missing configuration files that are gitignored are the most common blockers.
Look for architecture decision records, API documentation, or any notes from the previous team about known issues or workarounds. The absence of documentation is itself a finding, and documenting what you learn during the audit is one of the most valuable outputs of this process.
Step 9: Technical Debt Scoring
Once you have completed the audit, assign a severity score to each finding. We use a simple three-level system: critical (security vulnerabilities, crash-causing issues, broken builds), moderate (outdated dependencies, missing tests for critical paths, no CI/CD), and low (code style issues, minor deprecations, documentation gaps).
Compile the findings into a prioritized backlog. The first sprint after an audit should focus exclusively on critical items. Moderate items become part of the ongoing maintenance cadence. Low items are addressed opportunistically when working in the affected code areas.
This structured approach transforms an unknown codebase into a known quantity with a clear path forward. You may not like everything you find, but at least you will know exactly what you are dealing with.
Inheriting a codebase you did not build is one of the hardest challenges in mobile development. DEVSFLOW Maintenance specializes in taking over existing apps, auditing them thoroughly, and building a maintenance plan that keeps them healthy. Talk to our team about your inherited app.