Mobile — App Performance Optimization
Battery Drain: Common React Native Mistakes
Direct answer
The React Native mistakes that drain batteries fastest are polling timers that keep running when the app is backgrounded, location tracking configured at maximum accuracy with no distance filter, animations and re-render loops keeping the CPU awake for invisible UI, and chatty unbatched networking that repeatedly powers up the radio. The common thread is work continuing when nothing user-visible justifies it. Fixes are mostly about lifecycle awareness: tie timers, subscriptions, and sensors to AppState, batch network traffic, and verify with the platform energy profilers.
Battery drain is the performance problem users punish hardest — the OS literally names your app in its battery settings — yet it produces no crash report and no stack trace. These are the specific mistakes I keep finding when clients ask me why users say their app eats battery.
Key facts, with sources
- The median crash-free session rate across mobile apps in 2025 was 99.95%, with top-performing teams at the 75th percentile holding 99.99%. (Luciq Mobile App Stability Outlook 2025)
- Compared with JavaScriptCore, the Hermes engine cuts typical React Native app startup from about 4.5 seconds to about 2.0 seconds, memory from about 185 MB to 136 MB, and engine app-size overhead from about 12 MB to 8 MB. (OneUptime Blog)
- Real-world production migrations to React Native's New Architecture report around 43% faster cold starts, 39% faster rendering, and 26% lower memory usage. (RapidNative)
- Shopify's performance bar for its React Native apps is critical screens loading in under 500 milliseconds at the 75th percentile, alongside over 99.9% crash-free sessions. (Shopify Engineering)
- Published optimization case studies include Discord cutting app startup time in half and Coinbase reporting an 80% funnel performance improvement after its React Native rewrite. (CatDoes)
Why battery bugs escape your test cycle
Battery problems are invisible in development because nobody develops for eight hours on one charge — the device sits on a cable while you iterate. The damage shows up in aggregate on user devices: the OS attributes energy use per app, users check the battery screen, see your name near the top, and uninstall. Android additionally tracks excessive wakeups and background activity as vitals that can affect store visibility, so drain is a distribution problem as well as a reputation one.
The root cause is almost always the same shape: work that continues when its purpose has expired. A timer serving a screen nobody is looking at, a socket held open for an app in the background, a sensor subscription outliving its feature. Every fix in this post is a variation on making work stop when its reason stops.
Polling that never sleeps
The classic offender is setInterval started at app launch and never reconsidered: refresh a feed, re-check a status, sync a cart — every thirty seconds, forever, including while backgrounded, where each tick can wake the JS engine, hit the network, and power the radio for data nobody sees. Multiply by every screen that added its own timer and the app burns measurable energy doing nothing useful.
Every recurring timer should answer two questions: does this need to run when the app is inactive — almost always no — and is polling even the right mechanism, or should a push or socket deliver changes instead? At minimum, gate timers on AppState so they stop on background and resume on active, and prefer one owner for periodic sync over ad-hoc intervals scattered across screens.
import { AppState } from 'react-native';
useEffect(() => {
let timer: ReturnType<typeof setInterval> | null = null;
const start = () => {
if (!timer) timer = setInterval(syncStatus, 60_000);
};
const stop = () => {
if (timer) {
clearInterval(timer);
timer = null;
}
};
start();
const sub = AppState.addEventListener('change', (state) =>
state === 'active' ? start() : stop()
);
return () => {
stop();
sub.remove();
};
}, []);Location tracking at maximum everything
GPS is among the most power-hungry components in the phone, and the default posture of copy-pasted location code is worst-case: highest accuracy, continuous updates, no distance filter, running in the background because a permission prompt got approved once. For most product features — showing nearby items, tagging a check-in, sorting by distance — a single fix at coarse accuracy when the screen appears is entirely sufficient.
When you genuinely need continuous tracking, tune it like the scarce resource it is: request the lowest accuracy the feature tolerates, set a distance filter so updates fire on meaningful movement rather than on a schedule, and stop the subscription the moment the feature is not in use. In audits I make teams justify every background-location entitlement; a surprising number exist only because a tutorial included them.
CPU kept hot by invisible work
Rendering costs energy, and React Native offers several ways to render pointlessly. Infinite looping animations — spinners, pulsing badges, shimmer placeholders — that keep running after their content loaded or their screen lost focus force continuous frame production. Re-render storms from over-broad state subscriptions keep the JS thread busy even when the visible UI is static. Keep-awake flags enabled for a video screen and never released prevent the display from ever resting.
The discipline is lifecycle symmetry: everything that starts must have a defined stop. Animations tie to screen focus and stop when data arrives; a screen that is not focused should trigger near-zero renders — verifiable with re-render highlighting while you navigate elsewhere; every keep-awake acquire has a release in a cleanup path. None of these fixes is hard; what is hard is noticing, because the UI looks identical either way.
Networking that keeps the radio awake
Mobile radios have a cost profile developers rarely think about: powering up for a transmission is expensive, and the radio stays in a high-energy state briefly after each transfer. Many small, scattered requests are therefore far worse than the same bytes sent together — an app that fires analytics events individually, refreshes three resources on three timers, and heartbeats a socket every few seconds keeps the radio hot more or less permanently.
Batch what can be batched: queue analytics and flush on an interval or on background transition, coalesce related refreshes into one request cycle, and let the platform's background-transfer mechanisms schedule non-urgent uploads efficiently. Retry logic needs special scrutiny — a failing endpoint retried in a tight loop with no exponential backoff converts one outage into hours of radio-and-CPU burn on every affected device, which is exactly when thousands of devices are affected simultaneously.
Verifying drain before and after fixes
Battery work needs measurement like any other optimization, and the platforms provide the instruments. On iOS, the Xcode energy gauge shows live energy impact by category — CPU, networking, location, GPU — while you exercise the app, and Instruments can attribute sustained CPU to specific call stacks. On Android, the Studio profilers show CPU and network activity over time, and system-level battery statistics reveal wakeups and background behavior across longer sessions.
My acceptance test for any battery fix is behavioral: background the app and confirm CPU, network, and location activity actually go quiet within a few seconds; leave the app idle in the foreground and confirm renders and requests stop. Then watch production signals — OS-reported battery attribution, Android vitals, and review language about heat and battery — to confirm the fleet-wide trend follows the lab result.
When to hire senior help
Bring in a senior performance specialist when your crash-free rate sits below roughly 99.9%, startup exceeds a couple of seconds on mid-range Android, or the team lacks profiling experience with Hermes, the JS thread, and native tooling. Performance rescue work is diagnostic in nature, so a short expert engagement typically finds the handful of hot spots that in-house teams spend months guessing at. If your stack includes React Native + Python + AI, a senior engineer who owns the full product beats coordinating multiple juniors.
Bottom line
Dhairya Senjaliya ships Mobile — App Performance Optimization projects worldwide — book a scoping call to discuss your specific situation.
Common pitfalls to avoid
- ✕Profiling only on high-end iPhones in dev mode, then discovering the real user base on low-end Android devices experiences multi-second startups and dropped frames
- ✕Rendering long feeds with ScrollView or an untuned FlatList instead of a virtualized list like FlashList, blocking the JS thread during scroll
- ✕Passing new inline functions and object literals on every render without memoization, causing cascading re-renders that never show up until lists grow
- ✕Ignoring JS bundle size and startup path, shipping no lazy loading or inline requires, so time-to-interactive balloons as the app grows
Frequently asked questions
Why does my React Native app drain battery in the background?
The usual causes are timers and polling loops that keep firing after the app is backgrounded, open WebSocket connections with frequent heartbeats, location subscriptions that continue running, and analytics or sync requests waking the radio repeatedly. Tie all recurring work to AppState so it stops on background transition, and reserve genuine background execution for features that truly require it, using the platforms' scheduled background mechanisms.
How do I measure my app's battery usage during development?
On iOS, run the app from Xcode and watch the energy gauge, which breaks energy impact into CPU, networking, location, and GPU while you exercise real flows; Instruments attributes sustained CPU to call stacks. On Android, use the Android Studio profilers for CPU and network activity plus system battery statistics for wakeups. Verify that backgrounding the app makes all activity go quiet within seconds.
Do animations drain battery in React Native?
They can, when they run without purpose: infinite spinners and shimmer loops that continue after content loads, animations on unfocused screens, and re-render storms that keep the CPU producing frames for static UI. Continuous rendering prevents the chip from idling. Tie animations to screen focus, stop placeholders when data arrives, and confirm with re-render highlighting that unfocused screens are not still rendering.
Which performance metrics should we actually track?
Crash-free session rate (the 2025 median is 99.95%, and below 99.8% is a red flag), cold start time, time-to-interactive, P75 screen-load time, and frame rate during scrolling. These need real-user monitoring in production, not just lab measurements.
Why is our React Native app slow on Android but fine on iOS?
The usual causes are testing only on flagship devices, JS-thread-blocking work, unvirtualized lists, and running an old React Native architecture or the JSC engine. Hermes and New Architecture migrations show measured gains of roughly 26 to 43% on memory and cold start, so upgrading the foundation is often the highest-leverage fix.
Does performance really affect revenue or is it an engineering vanity metric?
It compounds directly into acquisition and retention: crashes and slowness drive uninstalls and one-star reviews, and lower ratings measurably cut store conversion rates. That is why top consumer apps hold themselves to 99.99% crash-free sessions and sub-500ms screen loads.
Bottom line: Dhairya Senjaliya ships Mobile — App Performance Optimization projects worldwide. Book a scoping call at https://dhairyasenjaliya.com/#book-call.