Android build time audit before you buy faster laptops
An Android build time audit can show whether slow builds come from module setup, annotation processors, or bloated tests before you spend on hardware.

Why slow builds are not always a laptop problem
A faster laptop can shave a few seconds off a build. It usually doesn't fix the part that wastes the most time: work the build never needed to do.
If one small code change triggers half the app to rebuild, new hardware only hides the problem for a while. The real issue is often the shape of the project, the way modules depend on each other, or the extra tools that run on every edit.
Start by separating clean builds from incremental builds. Clean builds matter, but most developers don't run them all day. They change one file, hit build, fix an error, run tests, and repeat. If incremental builds are slow, the team pays that cost again and again.
A clean build that takes 12 minutes is annoying. An incremental build that takes 90 seconds after every small edit is worse. Ten short waits across a day can do more damage than one long wait before lunch. The cost is not just time on the clock. People lose focus. After a 45-second pause, it often takes another minute to get back into the task.
In many projects, only a few modules create most of the delay. One shared module with broad dependencies can trigger rebuilds across the app. A heavy annotation processor can do the same. Teams then blame "Android builds" as a whole, even when the problem starts in one or two places.
It also helps to split local pain from release-only work. Some steps are slow but happen only in CI or only for release builds, such as shrinking, signing, or broad test suites. Those jobs matter, but they do not explain why everyday coding feels slow.
A simple frame works well:
- Clean build time shows the upper limit.
- Incremental build time shows the daily cost.
- A small number of modules often cause most rebuilds.
- Release tasks and local tasks need separate analysis.
Measure those first. Many teams can reduce Android build times without buying a single laptop.
What to measure before you change anything
One slow build tells you almost nothing. Teams often remember the worst day and then blame the laptops. That is a bad sample.
Use one normal week and log the builds people actually run while they work. Keep the tasks consistent. If one developer measures assembleDebug on a feature branch and another measures a full clean build after merging main, the numbers won't help.
A short set of repeatable checks is enough:
- incremental app build after a small Kotlin change
- build after a resource or manifest change
- clean debug build
- unit tests for one module
- one CI pipeline for a normal pull request
Total build time matters, but the Gradle phases matter more. Configuration, task execution, Kotlin compilation, annotation processing, packaging, and tests do not slow down for the same reason. If configuration is slow, project setup may be the issue. If compile time jumps after a tiny edit, module boundaries or generated code usually deserve a closer look.
Write down what changed before each rebuild. A one-line UI tweak should not wake up half the repo. A dependency injection change, schema update, or shared model edit often triggers more work. That note turns raw timing into something you can act on later.
Keep local results separate from CI results. Local builds depend on warm caches, recent commands, background apps, and machine state. CI has different limits. Fresh runners, network pulls, stricter checks, and cache misses can change the picture completely.
A plain spreadsheet is enough. Record the date, branch, command, clean or incremental status, change type, total time, and which Gradle phase spiked.
After a week, the cause usually looks much less mysterious. Many teams find that one processor, one oversized module, or one broad test task wastes more time than old hardware.
Look at module boundaries first
A tiny edit should not wake up half the project. If one small change triggers rebuilds across many modules, the problem is often the project shape, not the laptop.
Start with a few cheap tests. Change one line in a feature screen, one shared utility, one resource value, and one config flag. Then check which modules rebuild each time. You are looking for fan-out: a local edit that spreads much farther than expected.
Wide shared modules usually create the worst pain. Teams often drop common models, UI pieces, networking code, analytics helpers, and dependency injection setup into one place because it feels convenient. Over time, that module becomes a traffic hub. Since many features depend on it, even a small change forces rebuilds all over the app.
That kind of structure looks tidy on paper and feels awful in daily work.
Good module boundaries do one thing well: they keep common changes local. A feature screen edit should usually rebuild that feature and the app module that packages it. It should not trigger unrelated features, shared data layers, and test modules unless the change really crosses those boundaries.
During an Android build time audit, don't ask only how many modules you have. Ask what happens when one of them changes. More modules can help, but only if the dependency graph stays narrow and predictable.
Check annotation processors and generated code
Annotation processors often add more build time than teams expect. In a typical Android project, they run during the same tasks developers trigger all day: build, install, and test. If your local builds are slow, count which processors run on those common paths before you blame the laptop.
Start with the processors that touch many modules or generate a lot of code. A small processor in one feature module may not matter. A processor tied to dependency injection, database schemas, image loading, or JSON adapters can affect almost every edit.
KAPT deserves extra attention. It still works, but it often makes incremental builds worse because a tiny code change can trigger more work than expected. If a module uses KAPT, check whether that module rebuilds too much after small edits. Teams often find that one processor causes more waiting than several normal Kotlin files.
A quick review usually exposes the same problems:
- processors that run in many modules but save little time
- older libraries kept out of habit
- generated code that hides simple logic the team could write by hand
- KAPT setups that could move to KSP or plain Kotlin
Generated code is not bad on its own. Keep it when it removes boring, error-prone work or when the library solves a real maintenance problem. Room is a common example. Some serialization and injection setups also make sense. But if a processor saves a few lines while adding seconds to every build, the trade is weak.
Test changes one at a time. Remove one processor, or move one module off KAPT, then measure the same task again. If you change five things at once, you won't know what helped.
That slow, boring method works. One team I worked with cut local debug build time by a noticeable margin after removing a legacy view binding generator and replacing a small AutoValue setup with plain Kotlin data classes. The code got simpler, and the build got faster.
Reduce test scope without losing safety
A lot of Android teams waste time in tests that do not match the change they just made. If a developer edits a button label in one screen, running every unit test, integration test, and device test in the app is hard to justify.
Safety matters. Test scope still needs to fit the risk.
Split your test types on purpose. Unit tests should check small pieces of logic and run fast on common local changes. Integration tests should check how a few parts work together, usually at the module level. UI tests should prove that real user flows still work, but they are the slowest and most fragile, so teams should use them with more care.
A practical rule is simple:
- Run unit tests locally for the module you changed.
- Run nearby integration tests when the change touches data flow, navigation, or wiring.
- Run UI tests locally only when you change screen behavior or the user flow itself.
- Run the full device test suite in CI, not on every laptop.
- Schedule broad regression runs daily or before release, not after every tiny edit.
This alone can remove a surprising amount of waiting. Many teams accept slow feedback loops because no one wants to reduce coverage and take the blame later. That fear is understandable, but it often leads to a lazy default: run everything, every time, for every change.
That is not safer. It is just slower.
Run a one-week audit step by step
A useful Android build time audit stays small. Don't start by timing every build on every machine. Pick one task that frustrates the team and repeat only that task for a week.
A good example is a common local workflow: edit one feature screen, change a string or layout, rebuild, and run the app on a device. That is the loop people actually feel.
Write down the baseline before you touch anything. Time the same task three times and note both the average and the slowest run. If one build takes 2 minutes and another takes 5, that gap tells you something. You may have more than one issue.
Then work through the week in small steps.
On day 1, freeze the workflow. Use the same branch, the same laptop, and the same steps each time. Record full build time, incremental build time, and how much time tests add.
On day 2, inspect module boundaries. If a small UI edit triggers rebuilds across unrelated modules, fix one boundary problem and stop there. Then rerun the exact same workflow and log the new number.
On day 3, check annotation processors and generated code. Remove or replace one heavy processor if you can, or limit where it runs. Measure the same task again.
On day 4, cut local test scope. If every small UI change runs broad test suites, move some of that work out of the default local path.
On day 5, review the log and keep only the changes that save real time. A fix that saves 8 seconds may feel nice. A fix that saves 2 minutes changes how people work.
One rule matters more than most teams expect: change one thing at a time. If you adjust modules, switch processors, and trim tests on the same day, you won't know what actually helped.
Save the notes in a simple table. After one week, you should know which fixes are worth keeping, which ones barely move the numbers, and whether hardware is still part of the problem.
Common mistakes that hide the real cause
Teams often blame Gradle or old laptops, but the slowdown usually starts in the project itself. A build audit often exposes a few habits that seem sensible at first and then quietly add minutes to daily work.
One common mistake is splitting code into more modules without checking the dependency graph. More modules do not always mean faster builds. If one change in a shared module triggers half the app to rebuild, the extra structure didn't help. I have seen teams turn one app into 30 modules and still rebuild most of them after a small UI change.
Another trap is adding code generation to save a little manual work. Annotation processors can be expensive, especially when they run across large parts of the project. A generated class may save ten lines of code, but if it adds 20 seconds to incremental builds, the trade is poor.
Teams also mix up local build delays with CI delays. These are often different problems. Local delays may come from annotation processors, bad module edges, or oversized debug test tasks. CI delays may come from clean builds, slow cache restores, emulators, or a branch policy that runs too much on every push. If you treat both as one issue, you can spend weeks fixing the wrong thing.
Test scope gets bloated in a very ordinary way: nobody wants to be the person who removed a test job. So every branch runs everything. Unit tests, integration tests, UI tests, lint, screenshots, and full release builds all fire at once, even for a copy change. Safety matters, but habit is not a test strategy.
The most expensive mistake is buying hardware first. Faster laptops can hide pain for a while, but they rarely remove waste. If bad module boundaries or slow processors add a minute to each build, that minute follows the team onto every new machine.
Measure first. Spend later.
Quick checks before buying new hardware
A faster laptop can cut a few seconds. It will not fix a build setup that rebuilds too much, runs code generation in the wrong places, or starts heavy tests for small edits.
Start with a simple question: what should rebuild after a tiny change? Edit one string or one layout file and watch what Gradle touches. If that edit pulls in many modules, your boundaries are too loose or your dependencies point the wrong way.
Before anyone opens a laptop catalog, check a few basics:
- Edit one file in a feature module and confirm Gradle rebuilds only that module and the app module that depends on it.
- Review shared modules. If they hold UI helpers, networking code, test fixtures, and common models all at once, they will change often and force rebuilds everywhere.
- Review annotation processors, especially KAPT or KSP tasks. Keep them only in modules that actually need generated code.
- Split local debug work from slow device tests.
- Keep broad regression checks in CI, where they belong.
A quick example makes this obvious. If a developer changes a button label in a checkout screen, the build should rebuild the checkout feature and then package the app. It should not rerun database generation in unrelated modules, rebuild shared code that almost never changes, and launch device tests for login, search, and settings.
Teams buy hardware because the pain is real. The mistake is assuming the laptop caused it.
A realistic team example
A small Android team is sure it needs faster laptops. One developer changes a checkout screen, presses Run, and waits while six modules rebuild. The change is tiny. The wait is not.
When the team times the build, the slow part is not the screen code. It is one shared module that almost every feature depends on. Over time, the team dropped too much into that module because it felt convenient: common UI helpers, analytics wrappers, request models, and utility code. Now a small edit in one place wakes up a large part of the app.
Two annotation processors add even more delay. One handles dependency injection. The other generates database code. Neither is a problem on its own, but both sit close to the shared layer, so a small feature change triggers extra work again and again.
The team does not buy new hardware yet. First, they pull unstable code out of the shared module and move it closer to the features that change often. The shared layer keeps only code that rarely changes. After that, an edit on the checkout screen usually stays inside the checkout feature.
They also change how they test locally. Their old habit was simple and expensive: every local debug build came with device tests. That sounded safe, but it slowed down daily work. They keep fast unit tests in the local loop and run device tests when a change touches navigation, storage, permissions, or real device behavior.
The result is not magic. It is less wasted work. A build that took around 4 minutes drops closer to 90 seconds for a common feature edit. Full builds still take time, and they should. But the day-to-day loop feels much lighter.
The laptops never changed. After the audit, the same machines feel fast enough because the project stops rebuilding code nobody touched.
What to do next
Turn the audit notes into a short plan the team can finish in two or three weeks. Give each item one owner, one date, and one number to watch. If nobody owns the change, it slips. If nobody measures it, people guess.
Start with the biggest rebuild trigger you found. That is often a weak module boundary, an expensive annotation processor, or tests that run far beyond the code that changed. Pick one fix, ship it, and measure again on the same branch, with the same task, on the same machine setup.
A simple plan usually looks like this:
- fix the biggest rebuild trigger first
- remove, replace, or isolate one expensive annotation processor
- move broad tests out of the default local workflow
- compare build times before and after each change
- revisit laptop upgrades only after the numbers stop moving
Delay laptop purchases until your metrics stay flat for at least a week or two. Faster hardware can help, but it can also cover up waste. If a tiny code change still forces half the project to rebuild, new laptops only make the same mistake finish a bit sooner.
If your team wants an outside review, Oleg at oleg.is offers Fractional CTO help for build audits, architecture cleanup, and practical AI-first development workflows. That kind of outside view can help when the team is too close to the code and every delay starts to feel normal.
A good Android build time audit should end with a short fix list, clear ownership, and less waiting every day.
Frequently Asked Questions
Do we really need faster laptops to fix slow Android builds?
Usually no. Check what rebuilds after a tiny edit first. If one small change wakes up many modules, new laptops will only hide that waste for a while.
What should we measure first in a build time audit?
Look at incremental build time for a normal edit-run loop. That number shows the cost your team pays all day, not just during a full rebuild.
Why do incremental builds matter more than clean builds?
Developers spend most of the day making small edits, not running clean builds. A 60 to 90 second wait after every change breaks focus faster than one long clean build now and then.
How can I tell if module boundaries are the real problem?
Edit one line in a feature and watch which modules rebuild. If unrelated features or shared layers rebuild too, your dependency graph is too wide.
What kind of module usually causes the most rebuilds?
Shared modules often cause it. When one module holds common UI code, models, analytics, and helpers, many features depend on it, so even a small edit spreads across the app.
Are annotation processors a common cause of slow builds?
Very often, yes. Dependency injection, database, and serialization processors can add a lot of work to common build paths, especially when they run in many modules.
Should we replace KAPT right away?
If a module uses KAPT and small edits trigger too much recompilation, moving to KSP or plain Kotlin can help. Test one module at a time so you can see whether the change actually saves time.
How should we trim local test scope without getting risky?
Keep unit tests close to the code you changed and run broader tests when the change touches data flow, navigation, storage, or real device behavior. Let CI handle the full device suite for normal branch work.
How long does a practical build audit take?
One week is enough for a useful audit. Use the same machine, the same workflow, and the same commands, then change one thing at a time and log the result.
When does it make sense to buy faster laptops?
Wait until your numbers stop improving after module, processor, and test fixes. Buy hardware only when the project rebuilds the right things and the remaining delay still hurts daily work.