In a React Native app, how much time does one frame get at 60 Hz and at 120 Hz, and what does dropping a frame mean?
answer
- one second divided by the refresh rate
- about 16.7 ms at 60 Hz
- about 8.3 ms at 120 Hz
- missed deadline shows the old frame again
- 200 ms of work is about 12 frames
basics
~20 sAt 60 Hz each frame has about 16.7 ms and at 120 Hz about 8.3 ms. A dropped frame is one not ready by its deadline, so the screen shows the previous image again and motion stutters.
solid answer
~40 sThe budget is simply one second divided by the refresh rate: about **16.7 ms** at 60 Hz and about **8.3 ms** at 120 Hz. In React Native two threads have to hit that deadline: the **JS thread**, where React renders and your handlers run, and the **UI (main) thread**, which draws native views and runs scrolling and native transitions. If the frame's work is not done in time the display repeats the last frame - a **dropped frame** - and a run of them reads as jank. A 200 ms JS task at 60 Hz costs roughly 12 frames, during which JS-driven animations freeze and touches wait. A 120 Hz display does not give you more time; it halves the budget, so code that just fit at 60 Hz can start dropping frames.
go deeper
Recall the numbers: about 16.7 ms per frame at 60 Hz and about 8.3 ms at 120 Hz. A dropped frame means the old image is shown again because the new one was late.
Explain that the JS thread and the UI thread each have to meet the deadline, and give examples of what freezes when only one of them is busy.
Show you treat the budget as a per-frame deadline: hunt spikes rather than averages, test on 120 Hz hardware, and measure on release builds before optimising.
Discuss how the frame budget becomes a product constraint, for example which interactions must stay smooth on low-end devices and how that shapes where work is allowed to run.
## Where the numbers come from A screen refreshes at a fixed rate, and each refresh shows one still image - a **frame**. Motion is an illusion built from a steady stream of frames, so smoothness depends on producing every frame before the display asks for it. The time available for one frame is the **frame budget**: | Refresh rate | Arithmetic | Budget per frame | |---|---|---| | 60 Hz | 1000 ms / 60 | about **16.7 ms** | | 90 Hz | 1000 ms / 90 | about **11.1 ms** | | 120 Hz | 1000 ms / 120 | about **8.3 ms** | The React Native docs frame performance around this number: iOS and Android devices display at least 60 frames per second, which leaves at most 16.67 ms to do all the work for a frame. On a 120 Hz panel the same arithmetic leaves about half that. ## What "dropping a frame" means If the work for a frame is not finished when the display refreshes, nothing new is ready, so the screen shows the **previous frame again**. That is a **dropped frame**. One drop is rarely visible; several in a row read as a stutter, a frozen animation, or a tap that seems ignored. The docs give a concrete scale: a state update on a root component that makes React re-render expensive subtrees for **200 ms** costs about **12 frames** at 60 Hz - and every JavaScript-driven animation freezes for that time. Key points to hold on to: - The budget is a **deadline per frame**, not an average. A screen that averages 10 ms per frame but has one 80 ms spike still stutters. - A higher refresh rate **shrinks** the budget. Moving from 60 Hz to 120 Hz halves it, so work that fit comfortably can start missing deadlines. - Dropped frames are not errors and nothing crashes; the app simply looks and feels unresponsive. - Work that finishes quickly but runs **every frame** (a JS handler on each scroll event, for example) competes for the same budget. ## Two threads, two budgets In React Native the frame deadline applies to more than one thread, which is what makes jank here different from a single-threaded web page: 1. **The JS thread** runs your React components, effects, event handlers and any JavaScript-driven animation. If it is busy past the deadline, React cannot respond to touches or commit updates that frame. 2. **The UI (main) thread** owns the native views. It draws them, runs native scrolling and runs native transitions such as those of `@react-navigation/native-stack`. Each thread can miss frames independently. A frozen JS thread does not stop an already-rendered `ScrollView` from scrolling, because the scroll lives on the main thread; the scroll events are delivered to JS, but receiving them is not required for the scroll to happen. Conversely, a main thread struggling to draw an expensive view hierarchy stutters even when JavaScript is idle. Telling the two apart is the first diagnostic step, and the in-app **Perf Monitor** reports frame rates for this purpose. ## Why the budget shapes everyday decisions The number turns into rules of thumb: - Keep synchronous JS work in a single task well under the budget; split long jobs into small chunks so input and rendering can interleave. - Treat a **120 Hz** device as the stricter test case, not the easier one. - Keep animations and gestures that must stay smooth off the JS thread where the animation library allows it, so a busy JS thread cannot freeze them. - Judge frame rates on a **release build**: development mode adds extra runtime work on the JS thread and makes the budget look far tighter than it is for users. ## Common mistakes - Treating 60 fps as an average to hit over a second rather than a deadline every frame. - Assuming a 120 Hz phone gives the app more room; it gives less time per frame. - Blaming "React Native is slow" before checking which thread missed the deadline. - Measuring jank in a debug build and optimising code that is fine in release. The frame budget is the unit every later performance conversation is measured in: re-render cost, list windowing, animation drivers and startup work all come down to whether a thread finished its share of work before the next refresh.
- Why can a React Native screen scroll smoothly while a button on it ignores taps?Native scrolling of an already-rendered `ScrollView` runs on the UI thread, so it keeps its frame budget even when the JS thread is blocked. The button's `onPress` needs the JS thread to process the touch and update state, so taps queue until JavaScript is free again.
- A team says their screen is fine because it averages 55 fps. Why might users still complain?Averages hide spikes. The budget is a per-frame deadline, so a single 100 ms task costs about six frames at 60 Hz and reads as a visible hitch, even when the one-second average barely moves. Look at the worst frames and repeated stutters, not only the mean.
A frame budget is a train timetable: a train leaves every 16.7 ms whether or not the cargo is loaded. Miss it and the platform shows yesterday's train again; a faster timetable (120 Hz) means less loading time per train, not more.
saying these in an interview costs you the question
- A 120 Hz display gives the app more time per frame
- 60 fps only has to be reached as an average over a second
- Dropped frames are errors that eventually crash the app
- All React Native work, including scrolling, runs on the JS thread
- A 200 ms task costs one frame because frames are batched