Every FiveM server owner has lived the same afternoon: population climbs past sixty, everything feels fine, and then the server's baseline tick time starts creeping toward the red. Ten minutes later you are restarting roles back and blaming the map. Nine times out of ten, the cause is a script loop that was written to be correct first and fast never.
This guide collects the optimization checklist we run on every resource we ship. It is ordered by blast radius — the first sections fix problems that alone account for most of the bad tick time in the wild.
Rule one: everything not constant is cached
The largest single win in most codebases: stop re-reading configuration and re-enumerating data inside loops. A loop that runs every frame does not need to consult the database every frame, and it certainly does not need to parse JSON from disk every frame.
- Server-side: load config tables once at startup and hold them in memory. If the config can change at runtime, reload explicitly from an admin command — never on a timer.
- Client-side: wrap repeated native calls in local references where the native is expensive, and skip entire calculation blocks when the player is not actively using the relevant UI.
- Dark pattern to hunt: any wait loop running
while truewhere the shortest sensible sleep is 0ms instead of 250–1000ms. Most "idle" loops do not need frame-rate precision.
If you cannot say precisely what a loop isWaiting for, it is probably Waiting for nothing while burning your frame budget.
Profile before you refactor
Guessing at performance is how teams spend a week rewriting a resource and gain 0.2ms. The profiler tells you the truth in five minutes.
- Open the resmon overlay on the server (
resmon 1) and identify the resources above 0.1ms baseline. - Use the built-in profiler to record thirty seconds of normal gameplay plus one busy interaction (buying a vehicle, changing jobs).
- Sort by self-time, not total-time. Self-time points at the actual function doing the damage; total-time points at frameworks around it.
What a healthy profile looks like
A well-built resource idles at or near 0.00ms and spikes only during the events it exists to handle: a purchase, an inventory tick, an animation start. If your resource shows a flat 0.4ms baseline while nothing is happening, somewhere a loop forgot to sleep — or an event is registered per-player instead of once.
Network traffic: the budget you forget you have
CPU gets the blame, but the network waterfall is what actually kills responsiveness at scale. Every table sync, every HUD state push, and every "send full state on join" pattern competes for the same pipe.
- Send deltas, not states. If a garage count changes from 3 to 4, sync the one number — not the array that contains it.
- Throttle UI updates. The HUD does not need 60 updates per second. One per 250ms is indistinguishable to players and roughly free by comparison.
- Audit join-time sync. Some servers send hundreds of kilobytes per joiner because every resource dumps its full state. Multiply that by a busy queue and your connection tab tells the story.
Patterns that scale, patterns that do not
Scales
- Event-driven logic — do work when the state actually changes.
- Batched database writes — collect dirty rows, flush on a timer or on player disconnect.
- Pre-computed views — anything the UI renders often should be materialized, not recomputed per tick.
Does not scale
- Per-frame iteration over every player for every check ("for each player, for each property, check distance") — invert it with a spatial index or a simple coarse filter.
- Client-side loading of every resource for every player, regardless of job or role.
- Synchronous database calls inside hot loops.
A pre-release checklist
Before any script ships — ours or yours — it should clear every line of this list. Run it as a habit, not a postmortem:
- Resmon baseline at 0.00–0.05ms while idle, with and without 30+ connected players.
- No
while trueloop sleeps shorter than 250ms unless a frame-precise visual demands it. - No per-frame native calls that could be cached at startup.
- Network events measured — the join path under 100KB total.
- Database writes batched, reads cached.
- One full profiler pass to confirm no single function holds more than 0.05ms of self-time.
Optimization is not a heroic rewrite at the end. It is a set of defaults held from the first line of code — and once your baseline is clean, everything above it gets easier: features, population, and the sales that follow a server that simply feels smooth.