The bug report said Apex Moto “is smooth, but it lags and stops for a long time.” That's the hardest kind of performance bug. The game ran at full speed most of the time, then froze, briefly but noticeably, at random points in the race. In a motorbike game that is the worst place to freeze.

Apex Moto is the racing headline of Game Collection: a night-city race against up to seven riders, built with React Native, Skia for drawing and Reanimated for animation. This is how we found the stalls and removed them.

Measure first

We didn't guess. We added a temporary logger to the race that recorded two things and sent them to a small local server while we played:

  • JavaScript stalls: a timer that should fire every 50 ms, which shows how late it really fires. Lateness means the JavaScript thread was blocked.
  • Frame gaps: the time between frames on the UI thread, where the race is actually drawn.

The data showed that the "long stops" were JavaScript stalls of 140 to 390 ms during a race. A stall that long is plainly visible. Three separate causes came out of the logs.

Three causes and three fixes: 120 Hz doubled work, HUD re-renders, sounds built mid-race

Cause 1: 120 Hz screens did twice the work

The race physics ran once per frame. On a 60 Hz screen that is 60 steps a second. On a 120 Hz ProMotion iPhone it is 120, so the newest, fastest phones did twice the work for no visible gain.

The fix: the physics step is now capped at 60 Hz. Each frame adds its elapsed time to an accumulator. The physics only advances once at least about 10 ms have built up, using that real elapsed time, with a ceiling of 50 ms. Movement stays correct at any refresh rate, and a 120 Hz screen no longer doubles the cost.

acc += frame.timeSincePreviousFrame ?? 16;
if (acc < 10) return;              // not enough time yet: skip this frame
const dt = Math.min(0.05, acc / 1000);
acc = 0;
step(dt);                           // physics advances by real elapsed time

Cause 2: the speedometer re-rendered the screen

The speed, gear and race time changed constantly, and each change went through React state. In React, a state change re-renders the component that owns it, and in a busy game screen that can mean a lot of components. Those re-renders were the big stalls.

The fix: three changes.

  • The fast-changing numbers (speed, gear and time) are now written directly on the UI thread, with no React render involved.
  • Everything else on the HUD (position, lap, nitro) updates four times a second, which is plenty for numbers the eye reads.
  • The steering controls are memoised, and the rival riders and screen flashes live in their own small components, so they update without dragging the whole screen along.

Cause 3: race sounds were built the first time they played

Several race effects are generated in code instead of loaded from files. Each one was built the first time it was needed. Too often, that was mid-race: the first nitro boost or the first overtake.

The fix: every race sound is prepared in advance, while you are still in the garage choosing your bike, so the race itself never waits for one. We also stopped resending the engine loop's pitch and volume when they had barely changed. Tiny adjustments below what you can hear are now skipped.

The result

With all three fixes in, our logger showed no JavaScript stalls during a race in testing. We shipped the fixes as an over-the-air update. Testing was done on the iOS simulator, so we're watching feedback from real phones, Android included, before we call it finished.

The general lesson: in a React Native game, keep anything that changes every frame off React state, run the simulation at a fixed rate instead of the screen’s rate, and never create an asset in the middle of play.
SymptomCauseFix
Extra load on new iPhonesPhysics ran at 120 HzStep capped at 60 Hz, real elapsed time
Long freezes mid-raceHUD state re-rendered the screenUI-thread text, HUD 4 times/s, isolated layers
Hitch on first boost or overtakeSound built on first usePrepared in the garage; tiny loop changes skipped

Keep reading: How we rebuilt Cricket for a phone · Inside Lacspace Game Studio · 76 free offline games with no ads · How the app picks your next game

Game Collection is made by Lacspace Game Studio. This article describes the app as of 3 November 2026; games and features change with updates. Game Collection is free, with no ads and no in-app purchases.

Frequently asked questions

Why do some mobile games stutter on 120 Hz iPhones?

If a game runs its simulation once per frame, a 120 Hz screen doubles the work compared with 60 Hz. Running physics at a fixed rate, using real elapsed time, keeps the cost the same on any screen.

How do you avoid re-render stalls in a React Native game?

Keep values that change every frame, such as speed or a timer, out of React state. Write them on the UI thread (for example with Reanimated), update slower HUD values a few times a second, and isolate busy parts of the screen in their own components.

What is Apex Moto?

Apex Moto is a free motorbike racing game in Game Collection, raced at night against up to seven riders, with steering buttons, tilt and nitro. It has no ads and plays offline.

How did you measure the stutter?

With a temporary in-game logger that recorded how late a 50 ms JavaScript timer fired, which reveals thread stalls, and the gaps between frames on the UI thread, sent to a local server during real races.

Did the fix require an app update from the store?

No. It shipped as an over-the-air update, so players received it the next time they opened the app.