On a low end phone your app gets more time per frame and far less room to use it.
What you hit on weak hardware is a memory ceiling, not a speed limit. A cheap phone that still gets updates runs the same JavaScript a flagship runs, on a display that usually refreshes at 60Hz, and Android documents that as the loosest frame budget it publishes: 16ms per frame, against 8ms on a 120Hz phone. What shrinks is the amount of work that fits inside the window, and the amount of memory the app may hold while it works. Android sets a hard per-app heap limit that varies by device, returns an error when you pass it, and kills the process holding the most memory when the system runs short. Nothing in your code says any of those numbers out loud, which is why the symptom arrives as a crash report from someone else. If the project itself is new to you, start with what React Native is.
What follows is the order things give way in, the figures each platform actually publishes, and the one mistake that makes a fast phone look slow. Every quote below comes from Apple's, Google's or React Native's own documentation, read on 17 October 2026.
Start hereThe short version
The deadline is a property of the screen. The ceiling is a property of the phone.
Three things change on weaker silicon and none of them is the frame budget. The same render takes more of the window, so you miss frames you used to make. The heap you are allowed is smaller, because Android scales it to how much RAM the device has. And you are evicted from memory to make room for whatever is in the foreground, so the user comes back to a cold start instead of the screen they left.
What each store publishes about this is a mechanism rather than a minimum spec: a way to refuse the install, a way to read your own numbers per device model, and a threshold at which it starts calling your app unresponsive. Those three things are the whole toolkit, and all three are different on each platform.
What actually gets harder when the phone is slower?
Start with the thing that does not change. Android's slow rendering guidance says an app must render frames in under 16ms to reach 60 frames per second, that the window drops to 11ms at 90fps and 8ms at 120fps, and that overrunning by 1ms does not make the frame late: the Choreographer drops it entirely. A 60Hz panel hands you the most generous figure on that list, and the cheaper the phone the more likely that is the panel it has. The flagship in your pocket, running at 120Hz, is working to half of it. Whatever is wrong on the cheap phone, a tight deadline is not it.
What changes is how much fits inside the window. React Native's performance guide is blunt about where your code runs: for most apps, your business logic runs on the JavaScript thread, which is where your React application lives, where API calls are made and where touch events are processed. One thread. The guide's own illustration is a state change on a complex tree that takes 200ms and drops 12 frames, with any JavaScript-driven animation appearing to freeze for that whole time. That example is an illustration rather than a measurement, and the point survives either way: the same render costs more of the window on a slower processor, and the work does not get interleaved with anything.
Some things survive a blocked JavaScript thread and some do not, and the split explains most of what users report. The same guide notes that a ScrollView keeps scrolling while the JavaScript thread is locked up, because the ScrollView lives on the main thread and the scroll does not wait for your code to acknowledge it. Native stack navigators behave better than JavaScript ones for the same reason: the transition runs on the native main thread. So a complaint that scrolling is fine but tapping does nothing is a JavaScript thread problem, and a complaint that the whole screen stutters while nothing is happening is usually not.
One React Native note is specifically about weak graphics hardware. Moving a view can drop main-thread frames, and the guide says this is especially true on Android when you have text with a transparent background positioned on top of an image, or any other situation where alpha compositing would be required to redraw the view on each frame. Its fix is to let the platform cache the view as a texture, with a warning attached: do not overuse it or your memory use could go through the roof. That is the trade you will meet repeatedly on cheap hardware. Every fix for a frame problem spends memory, and memory is the thing you have least of.
React Native, Performance Overview, page dated 12 August 2026, read 17 October 2026
Why does it run out of memory before it runs out of speed?
Because the ceiling is hard and the slowdown is gradual. Android's memory management overview says that to maintain a functional multi-tasking environment, Android sets a hard limit on the heap size for each app, that the exact heap size limit varies between devices based on how much RAM the device has available overall, and that an app which reaches heap capacity and tries to allocate more can receive an OutOfMemoryError. There is no published table of limits per phone. The documented way to find yours is to ask the device at runtime, through a call that returns the number of megabytes available for your app's heap. That page carries a last-updated date of 19 May 2026.
Garbage collection is the part that connects memory back to dropped frames. The same page says the system decides when to collect, that you do not generally control it from your code, and that a collection landing in the middle of an intensive loop such as an animation can push your code past the recommended 16ms threshold for smooth frame rendering. It then names the pattern that causes it: allocating many objects in the innermost part of a loop on every frame. On a phone with a small heap you reach that state sooner, so the stutter and the crash have the same cause and arrive in that order.
Then there is being killed outright. Android's low memory killer page, dated 21 September 2026, says devices often run with very little free memory by design, and that the low memory killer daemon reacts to high memory pressure by killing the least essential processes to keep the system performing acceptably. It scores processes so background apps go first and system processes last. It also says trim callbacks have not been helpful at preventing low-memory kills, which is why Android deprecated most of them, and that there is no API for detecting native memory pressure events. What you are left with is an exit-reason API that tells you after the fact why the previous run died. iOS describes the same squeeze in its own words: Apple's launch-time guidance says that if someone has just played a memory-intensive game, your app was typically evicted from memory to give the foreground app more, and that your return is then a cold launch.
If you want to know what you are running on, an Expo project has one honest answer available. The Expo device library reports the device's total memory in bytes, with a caveat printed next to it that is the whole lesson: this is the total memory accessible to the kernel, but not necessarily to a single app. It also reports a device year class, which Expo defines by pointing at the open source library of that name rather than by publishing a scale of its own. Both numbers are fine for deciding whether to turn a feature off, and neither is a performance measurement. If you have never added a library to one of these projects, getting started with Expo covers the shape of it.
Check yours
What the documentation says about the phone you are worried about
Pick the amount of RAM on the device in the complaint. Every line below is a figure one of the two platforms publishes, not a benchmark.
Does Android call it a low-RAM device
Still inside the range Android's reference describes, which is 1GB or less of RAM.
Is it an Android (Go edition) phone
Inside the Android (Go edition) range, which Google puts at 1 GB of RAM or less on Android 8.1 or higher.
Could you keep the app off it
Google's worked example cuts at 512 MB, so a rule that catches this phone is a number you choose yourself.
Three caveats that come with these figures. Android's reference says whether a device is low-RAM is ultimately up to the device configuration. A RAM rule reads the total memory the device reports, not the number on the box. And the rules do not apply to internal testers, which is the track most first builds go to.
What do Apple and Google count as a phone that is too slow?
Google answers with a number you choose. Play Console Help says that if you want to exclude devices by performance indicators, you can create targeting rules based on RAM size or System on Chip, and its worked example is a rule that excludes devices with less than 512 MB RAM. Two footnotes do the real work. RAM rules are based on the total memory the device reports rather than the branded memory, so the rule and the number on the box disagree, and a single device model can come back as partially excluded because its variants ship with different memory. And device exclusion rules do not apply to internal testers, so the track most first builds go to is the one no rule can filter.
Google also names an entry-level tier outright. Android (Go edition) covers entry-level devices running Android 8.1 or higher with 1 GB of RAM or less, and the Play Console carries an Android Go exclusions setting next to the other rules. Android's API reference puts the same figure in developer terms: the low-RAM check has existed since API level 19, returns true if this is a low-RAM device, and is hedged twice in one sentence, because whether a device is low-RAM is ultimately up to the device configuration and currently generally means something with 1GB or less of RAM. Treat it as a hint for turning features off, which is what the reference says it is mostly intended for: deciding whether to turn off certain features that require more RAM.
Apple answers with a capability instead of a number. Its support documentation says the App Store uses values in the required device capabilities property list key to work out the hardware-related capabilities an app needs, so that people only download apps their device supports. The published list of those keys, as it stood on 17 October 2026, is almost entirely hardware features: a camera flash, a gyroscope, NFC, telephony, a graphics API. Three of them are performance floors, two naming a chip generation and one a gaming tier. None of them is an amount of memory. So on iOS you can refuse a device because of what silicon it has, and you cannot refuse it for having too little RAM.
Both of these are blunt instruments, and reaching for either is a decision about your market rather than a fix. Google says an excluded device will not be able to see or install your app on Google Play at all, so the rule removes the customer rather than the crash. The ordinary answer is to support the phone and do less on it, which is why the low-RAM check and the device year class exist in the first place. Doing less on it is also a recurring cost rather than a one-off piece of work, and it belongs in the same budget line as what an app costs to maintain.
Google Play Console Help, view and restrict your app's compatible devices, read 17 October 2026
How do you test on a phone you do not own?
Rule one, before you buy anything: measure a release build. React Native's performance guide says JavaScript thread performance suffers greatly in development mode, that this is unavoidable because far more work happens at runtime to give you good warnings, and to always test performance in release builds. The same guide treats console logging as a serious bottleneck on the JavaScript thread and recommends stripping those calls from production builds even if your own code makes none, because a third-party library could. React Native's loading guidance adds the reason the gap is so wide: in release builds the JavaScript is compiled to bytecode ahead of time and loaded on demand, rather than parsed as the app starts. A debug build on a cheap phone is the worst measurement available, and it is the one most people take.
Rule two: use the field data you already have. Apple's guidance on shipping apps says the Xcode Organizer shows anonymised performance data from your users, including launch times, memory use and responsiveness, and that you can filter a metric by device and by median or high value. Launch time comes with a 50th and a 90th percentile. Hang rate is reported as the seconds per hour your app is unresponsive, counting only periods longer than 250 ms. Apple then says the sentence this entire page exists to repeat: its operating systems support a broad variety of devices with different hardware capabilities, and code that performs flawlessly on one hardware model can hang on another. Google's device catalog is the same tool with a different shape, listing per-model metrics for your app alongside a breakdown of that model by memory and Android version.
Rule three: know what each platform calls a failure, because the thresholds are not close. Android separates a slow frame, which takes between 16ms and 700ms, from a frozen frame, which takes between 700ms and five seconds, from an application-not-responding event past five seconds, and says no frame in your app should ever take longer than 700ms. Apple starts counting a hang at 250 ms. If you are trying to agree with a client on whether an app is acceptable, those are the published lines to argue from, and they are the reason the same app can be inside tolerance on one store and outside it on the other.
Rule four: if you cannot get the handset, simulate the condition. Apple is direct that you get higher-fidelity measurements by profiling on a device rather than a simulator, and that if your data shows the app performing poorly on a particular class or model of device, you should profile on that device. Where that is impossible, Android documents a memory stress tool you push to the phone over adb and run for a set number of seconds against a set number of megabytes, specifically so you can reproduce a memory-stressed environment and write tests against it. It also lists what you will see when you do: a native abort, a core dump, or a trim callback. Fixes for all of this often arrive as a version bump rather than as code you write, which is what the Expo SDK release notes are for reading before you start optimising by hand.
The same slow phone, described differently by each store
| The question | iOS, Apple's documentation | Android, Google's documentation | Same on both |
|---|---|---|---|
| How you keep the app off a weak phone | A capability key in the app's information property list | An exclusion rule in the Play Console device catalog | No |
| A RAM number you can set | No RAM key in the published capability list | Yes, a rule on the total memory the device reports | No |
| Performance tiers you can require | Three, covering an iPhone chip floor, an iPad chip floor and gaming | None by tier, you name a System on Chip instead | No |
| What it calls unresponsive | A hang, counted only past 250 ms, reported as seconds per hour | A slow frame past 16ms, frozen past 700ms, an ANR past 5s | No |
| Per-device-model field data for your own app | Xcode Organizer, filtered by device and percentile | Play Console device catalog, with a RAM split per model | Yes |
| What the documentation tells you to do about it | Profile on the device that is slow, not the simulator | Reproduce the memory pressure, then test against it | Yes |
Where this lands if a builder generated your app
Newly is an AI app builder. You describe an app in plain English and it writes a real React Native and Expo project you own, with two-way GitHub sync so the code is somewhere you can read it. It uploads iOS builds to TestFlight and publishes to Google Play internal testing, and it can build a standalone Android APK. It does not submit to the App Store for you, because App Store Review Guideline 4.2.6 requires an app made with a generation service to be submitted by the provider of the app's content, which is you. It is $25 a month, with no free plan.
Two things follow from that for weak hardware, and they pull in opposite directions. The good one: because the output is an ordinary Expo project, everything on this page is available to you. The device library is one install away, a release build is a normal build command, and the field data in the Xcode Organizer and the Play Console device catalog is yours as soon as you have installs. Nothing here is gated behind having written the project yourself.
The bad one: no preview you watched while the app was being written is evidence about this. A simulator or an emulator borrows the memory and the processor of the machine running it, which is why Apple tells you to profile on a device rather than a simulator, and on the specific model once the data points at one. Nothing in a preview will ever show you a low-memory kill. So the honest first step after a complaint is to install the TestFlight or internal-testing build on the handset in the complaint and watch that, not to open the preview again.
One trap is worth naming because it is specific to how most generated apps reach their first testers. Google's device exclusion rules do not apply to internal testers. If you write a rule to keep the app off phones with too little memory, the people most likely to find the problem are the people the rule ignores. That is useful if you want the bug reports, and a nasty surprise if you assumed the filter was protecting everybody.
Questions people ask about React Native on older phones
It runs the same code with less room. The frame deadline comes from the display, not the chip, and a cheap phone is usually a 60Hz phone, which Android documents as 16ms per frame against 8ms at 120Hz. So the deadline is the loosest one on the cheapest hardware. What shrinks is how much work fits inside the window and how much memory the app may hold while it works. React Native's own performance guide says most of your logic runs on one JavaScript thread, and if that thread misses a frame the frame is dropped. Slower silicon means the same render takes more of the window. Read 17 October 2026.
Describe the app, then measure it on the worst phone you can find
Buy the cheapest handset your users actually carry, install the release build on it, and keep it in a drawer. It is the only test in this whole area that cannot be argued with, and it costs less than an afternoon of guessing.
Start building