Ticking & performance

How the server's tick loop actually spends its time, and how to write data packs that do not fight it — ticks, random ticks, spawn chunks, world seeds and the optimisation patterns that matter most.

The tick

The server advances the world in discrete steps called ticks, targeting 20 ticks per second — one tick every 50 ms. Everything the server does in a tick happens on one thread: block updates, entity AI, redstone, and every function your pack runs from the #minecraft:tick function tag.

If the work in a tick exceeds 50 ms, the server cannot keep up. It does not drop the work — it takes longer, and the measured rate falls below 20 TPS. This is why data-pack performance is really a question of how much work you add per tick, not how clever the logic is.

📌 Note

A tick is also the unit for /schedule, for time values, and for the age of most entities. 20 ticks = 1 second, 1200 ticks = 1 minute, 24000 ticks = one full Minecraft day.

What runs every tick

HookWhen it runsCost model
#minecraft:tickEvery tick, once per function in the tagPaid 20× per second, forever — the most expensive place to put anything
#minecraft:loadOn world load and on /reloadPaid once; the right home for setup and constants
/scheduleOnce, after a delayLets you run at a lower frequency than every tick
💡 Best practice

Most logic does not need to run at 20 Hz. A self-rescheduling function that runs every 20 ticks does 5% of the work of the same logic on #minecraft:tick, and for anything a player perceives as "immediate" a 2–4 tick period is usually indistinguishable.

data/example/function/slow_loop.mcfunction

# Runs once per second instead of 20 times per second.
function example:do_work
schedule function example:slow_loop 20t replace

Random ticks

Separately from the main loop, the game picks random blocks in each loaded chunk section and gives them a random tick. This is what grows crops, spreads grass, melts ice and decays leaves.

The rate is controlled by the randomTickSpeed game rule, which defaults to 3. That means 3 blocks are chosen per 16×16×16 chunk section per tick. The cost scales with the number of loaded chunks and with this value, so raising it is one of the easiest ways to make a server unplayable.

⚠ Gotcha

randomTickSpeed is multiplicative across every loaded section. Setting it to a large value to "speed up farms" applies that multiplier to the entire loaded world, not just the area you care about. Prefer targeted approaches — a function that grows specific crops — over a global rule change.

Spawn chunks & loading

A region around the world spawn point stays loaded whenever the server is running, whether or not any player is nearby. These are the spawn chunks. Historically they were the standard place to park always-running contraptions.

Loaded chunks are the main driver of per-tick cost: entities tick, block entities tick, and random ticks fire only in loaded chunks. Anything that increases the loaded set increases baseline load.

MechanismEffectNotes
Spawn chunksAlways loaded while the server runsSize and ticking behaviour have changed across versions and are configurable on modern servers
/forceload addKeeps specified chunks loadedThe explicit, controllable modern alternative — you choose exactly which chunks
Player render/simulation distanceLoads chunks around each playerScales with player count; the dominant term on a busy server
💡 Best practice

Prefer /forceload over relying on spawn chunks. It is explicit, it is per-dimension, it can be removed when the pack is done with it, and it does not depend on where the world spawn happens to be. Always pair forceload add with a matching forceload remove so a pack does not permanently inflate the loaded set.

The world seed

The world seed is the number that deterministically drives terrain generation, structure placement and several random systems. The same seed with the same generation settings and the same version produces the same world.

Two consequences matter for pack authors:

📌 Note

For a pack that needs a known layout, generate the terrain from your own custom world generation settings or place structures explicitly, rather than depending on a vanilla seed reproducing.

Optimising a data pack

In rough order of how much they typically matter:

1. Run less often

The single largest lever. Move work off #minecraft:tick onto a scheduled loop, or trigger it from an event instead of polling for one. An advancement with a trigger, revoked and re-granted, detects an event at the moment it happens and costs nothing while waiting — a polling loop pays every tick to find nothing.

2. Narrow selectors before you use them

Selector cost scales with how many candidates the server must consider. Constrain by the cheapest dimension first — a bounded volume — before filtering on scores, NBT or predicates.

Two ways to find the same entities

# Considers every entity in the dimension, then filters.
execute as @e[type=minecraft:armor_stand,tag=marker] run ...

# Bounds the search to a small volume first.
execute positioned 100 64 100 as @e[type=minecraft:armor_stand,tag=marker,distance=..8] run ...
💡 Best practice

Always specify type= when you can, and add limit= when you only need one match. @e with no type is the most expensive selector in the game.

3. Keep entity counts down

Every entity ticks. Marker armour stands, item displays and area-effect clouds used as position markers are convenient but they are not free at scale. Where a pack only needs to remember a position, storing coordinates in scoreboards or storage costs nothing per tick, while a marker entity costs something every tick forever.

4. Prefer one chained execute to many commands

Each command has parsing and dispatch overhead. Folding conditions into a single execute chain avoids running later commands at all when an early condition fails, and avoids re-resolving the same selector repeatedly.

5. Treat macros as a cost, not a convenience

Macro lines 1.20.2+ are compiled when invoked with new arguments rather than being free substitutions. They are the right tool for genuinely dynamic commands, but a macro on a hot path that could have been a plain command is wasted work every tick.

6. Watch recursion and function-call depth

Recursive functions and long function chains multiply quickly. The maxCommandChainLength game rule caps some of this, but a pack that relies on approaching that cap is already doing too much per tick.

Measuring, not guessing

Performance intuition is frequently wrong. Measure before and after any optimisation.

ToolWhat it tells you
/tick query 1.20.3+Current tick rate and average tick time
/tick sprint / /tick freeze 1.20.3+Run the world fast or hold it still while you inspect state
/debug start … /debug stopWrites a profiling report identifying where tick time went
Server-side profilersThird-party tools give per-function attribution on Paper-family servers
🎩 Trick

To attribute cost to your own pack specifically, use /tick freeze to stop the world, then /tick step a fixed number of ticks with your pack's tick tag empty versus populated. The difference is your pack's cost, isolated from ambient world load.

📌 Note

The relative weights above are general guidance drawn from how the tick loop is structured, not measurements from a specific server. Command and selector costs vary with entity counts, dimension, server implementation (vanilla versus Paper-family) and version — which is exactly why the measuring step is not optional.

Sources