# Godot stuttering: find the cause and fix it

> Why a Godot 4 game stutters and how to fix it: movement and camera jitter (physics interpolation), shader compilation hitches, spawn bursts, orphan nodes and slow scene loads, with GDScript you can copy and a way to check the fix.

Website: https://www.glitchless.gg/godot-stuttering.html · Godot 4.3 and later

"Stuttering" means two different things in a Godot game. Movement that judders while the frame rate looks fine is a timing problem between physics and drawing. A frame that freezes for a moment, often the first time something appears, is a hitch: one frame did too much work. The fixes are different, so find out which one you have before you change anything.

## The short answer

1. **Movement and camera jitter**: turn on physics interpolation, and move bodies in `_physics_process()`
2. **Shader compilation stutter**: update to Godot 4.4 or later, and draw new effects once behind the loading screen
3. **Hitches when many things spawn at once**: pool and reuse nodes, and draw crowds with MultiMesh or GPU particles
4. **Stutter that gets worse the longer you play**: free the nodes you remove; orphan nodes pile up
5. **A long freeze on a level change**: load the next scene in the background with `ResourceLoader.load_threaded_request()`

## First, find out which stutter you have

Run the game from the editor and open the Debugger panel. The **Monitors** tab graphs frame time, process and physics time, draw calls, objects and orphan nodes. The **Profiler** shows script time per function, and the **Visual Profiler** shows CPU and GPU time for each rendering step.

Then look at the shape. Steady numbers with juddering movement point to physics timing. A spike the first time something appears points to shader compilation. Spikes when things spawn point to node bursts. A line that keeps climbing points to a leak.

On Godot 4.4 and later, the **Pipeline Compilations** monitors count the shaders compiled while the game runs. If the Draw count goes up at the moment you see a hitch, that hitch is shader compilation.

The editor isn't what your players run, so check an exported build too. Godot smooths `delta` by default, which can hide a hitch, so time frames from the clock instead:

frame_log.gd (add as an autoload):

```gdscript
# Prints the typical, 95th percentile and worst frame every 5 seconds, in any build,
# with the pipelines compiled while drawing so far (Godot 4.4+). Orphan nodes are only
# counted in debug builds; a release export prints 0.
extends Node

var _frames := PackedFloat32Array()
var _last := Time.get_ticks_usec()
var _total := 0.0

func _process(_delta: float) -> void:
	var now := Time.get_ticks_usec()
	var ms := (now - _last) / 1000.0
	_last = now
	_frames.append(ms)
	_total += ms
	if _total < 5000.0:
		return
	_frames.sort()
	var n := _frames.size()
	print("frame ms: typical %.1f, 95th %.1f, worst %.1f | pipeline compiles %d, orphan nodes %d" % [
		_frames[int(n * 0.5)], _frames[int(n * 0.95)], _frames[n - 1],
		Performance.get_monitor(Performance.PIPELINE_COMPILATIONS_DRAW),
		Performance.get_monitor(Performance.OBJECT_ORPHAN_NODE_COUNT)])
	_frames.clear()
	_total = 0.0
```

## Movement and camera jitter

**What you see.** The frame rate is fine, but moving objects and the camera judder. It's worst on 120 Hz and 144 Hz screens, and when the camera follows a CharacterBody.

**Why.** Physics runs at a fixed rate, 60 ticks a second by default, while frames are drawn at the screen's refresh rate. When the two don't line up, some frames show a body in the same place twice and others skip ahead. A camera updated in a different step from the body it follows makes it worse.

Turn on **Physics Interpolation** in Project Settings (`physics/common/physics_interpolation`). Godot then draws physics objects between their last two positions, so movement is smooth at any refresh rate. It works for 2D from Godot 4.3 and for 3D from Godot 4.4.

Move bodies in `_physics_process()`, not `_process()`. When you set a position directly, for a respawn or a teleport, call `reset_physics_interpolation()` straight after, or the object glides across the screen to its new spot.

Once interpolation is on, a Camera2D takes care of itself: it ignores its Process Callback and moves with physics. In 3D, have the camera follow where the body is drawn by reading the body's `get_global_transform_interpolated()` in `_process()`.

A few things aren't interpolated: `GPUParticles2D` (use `CPUParticles2D` for particles attached to a moving body) and 3D bodies you create directly through the physics server.

player.gd:

```gdscript
# Project Settings > Physics > Common > Physics Interpolation: on
extends CharacterBody2D

const SPEED := 300.0

func _physics_process(_delta: float) -> void:
	velocity = Input.get_vector("left", "right", "up", "down") * SPEED
	move_and_slide()

func respawn(at: Vector2) -> void:
	global_position = at
	reset_physics_interpolation()  # jump there instead of gliding
```

## Shader compilation stutter

**What you see.** A freeze of a fraction of a second the first time an effect, enemy, material or particle system appears, and not the second time. It's often worst on a fresh install or after a driver update.

**Why.** The GPU needs a compiled pipeline for each combination of shader and render settings. Godot builds one the first time it draws that combination, and the frame waits for it.

Godot 4.4 added ubershaders and pipeline precompilation to the Forward+ and Mobile renderers (Vulkan, Direct3D 12 and Metal), which removes most of these hitches with no work on your side. If you're on an older 4.x, updating is the biggest single fix.

The Compatibility renderer (OpenGL and WebGL 2, used by web and many Android exports) doesn't get that. There, and for anything that still compiles mid-game, draw it once behind the loading screen: put each effect, mesh and particle system the level uses in front of the camera for a couple of frames while the loading screen covers the view. Objects outside the camera aren't drawn, so they don't compile.

Don't change settings that rebuild pipelines during play, such as MSAA or shadow quality, and don't switch on new material features mid-level (transparency, emission or a new texture slot on a StandardMaterial3D). Each combination is a different shader.

Godot 4.5's **Shader Baker** (an export preset option, Forward+ and Mobile only, not web) compiles shaders when you export. It makes the first launch faster, most on Direct3D 12 and Metal, but it doesn't stop hitches during play.

warm_up.gd:

```gdscript
# Behind the loading screen: draw each effect once so its shaders compile before play.
func warm_up(camera: Camera3D, effects: Array[PackedScene]) -> void:
	var in_view := camera.global_transform.translated_local(Vector3(0, 0, -4))
	var nodes: Array[Node] = []
	for scene in effects:
		var node: Node3D = scene.instantiate()
		add_child(node)
		node.global_transform = in_view
		nodes.append(node)
	# Two frames, so each one is drawn while the loading screen covers it.
	await get_tree().process_frame
	await get_tree().process_frame
	for node in nodes:
		node.queue_free()
```

## Hitches when many things spawn at once

**What you see.** A hitch every time a wave starts, a big explosion goes off or a pile of pickups appears.

**Why.** Every `instantiate()` and `add_child()` costs time: building the node tree, running `_ready()`, registering collision shapes. A hundred of them in one frame is a dropped frame.

Make what a level needs during loading and keep it in a pool. Hide and disable a node instead of freeing it, then reuse it.

Draw many copies of one mesh with a single `MultiMeshInstance3D` or `MultiMeshInstance2D`, and particles with one `GPUParticles3D` or `GPUParticles2D` node, instead of a node each.

If you must create a lot, spread it out: ten a frame over ten frames instead of a hundred at once.

pool.gd:

```gdscript
# Make bullets while the level loads, then reuse them.
extends Node2D

var _free: Array[Node2D] = []

func prepare(scene: PackedScene, count: int) -> void:
	for i in count:
		var b: Node2D = scene.instantiate()
		add_child(b)
		give_back(b)

func take() -> Node2D:
	if _free.is_empty():
		return null  # or grow the pool, outside a busy frame
	var b: Node2D = _free.pop_back()
	b.process_mode = Node.PROCESS_MODE_INHERIT
	b.show()
	return b

func give_back(b: Node2D) -> void:
	b.hide()
	b.process_mode = Node.PROCESS_MODE_DISABLED
	_free.append(b)
```

## Stutter that gets worse the longer you play

**What you see.** Smooth at first, then slower and more hitchy after a few levels or restarts, with memory that keeps climbing.

**Why.** `remove_child()` takes a node out of the scene tree but doesn't free it. Removed nodes keep their memory and resources, and an array, dictionary or autoload that still points at them keeps them alive.

Call `queue_free()` on nodes you're done with, or return them to a pool. Clear arrays, dictionaries and autoload variables that hold nodes from the last level.

Watch **Orphan Nodes** in the Monitors tab while you change levels in a debug run. It should drop back down each time. `Node.print_orphan_nodes()` lists them (debug builds only).

## A long freeze on a level change

**What you see.** The game freezes for half a second or more when a level starts or a big menu opens.

**Why.** `load()` and `change_scene_to_file()` load everything on the main thread, and nothing can be drawn until they finish.

Request the next scene in the background, keep the loading screen animating, and switch when it's ready. Keep `_ready()` in the new scene light too, since it runs in the frame the scene enters the tree.

loader.gd:

```gdscript
var _path := ""

func load_level(path: String) -> void:
	_path = path
	ResourceLoader.load_threaded_request(path)

func _process(_delta: float) -> void:
	if _path.is_empty():
		return
	if ResourceLoader.load_threaded_get_status(_path) == ResourceLoader.THREAD_LOAD_LOADED:
		get_tree().change_scene_to_packed(ResourceLoader.load_threaded_get(_path))
		_path = ""
```

## Check the fix

Measure the same way before and after: the same level, the same route, the same kind of build. Compare the typical frame, the 95th percentile and the worst frame from the logger above, not the average FPS, which hides hitches.

A hitch fix should bring the worst frame down. A jitter fix shows in how movement looks, so record a few seconds on the same screen before and after.

## Stutter that only your players see

Your machine isn't your players'. A pipeline that compiles in a few milliseconds on your GPU can take far longer on a mid-range phone, and some older Android GPUs can't build a shader at all on the Compatibility renderer: they log errors like `exceed GL_MAX_FRAGMENT_UNIFORM_VECTORS` and carry on without the effect. Nothing crashes, so nobody tells you.

[Glitchless](https://www.glitchless.gg/index.md) is an open-source addon for Godot 4.5 and later that times every frame of every player's session from the clock, names each hitch's likely cause (a pipeline compile, a scene change, a burst of new nodes) and tracks orphan nodes per level, split by device and version. Your coding agent reads the same data and can check its own fix: start the build with `--glitchless-measure=10` and it prints the numbers.

Ask your coding agent:

```
Use Glitchless to find what makes my Godot game stutter for players: list the worst hitches by cause and level, fix the top one, then measure the build with --glitchless-measure=10 to show the difference.
```

## Frequently asked questions

### Why does my Godot game stutter in the editor but not in the exported game?

The editor runs its own interface and debugger next to your game, and debug builds do extra checks. Measure an exported build before you change anything; stutter that only happens in the editor usually isn't what players see.

### Should I raise the physics tick rate to match a 144 Hz screen?

It can hide jitter on that screen, but it costs physics time every frame and doesn't help other refresh rates. Physics interpolation fixes jitter for every screen at the normal tick rate.

### Does the Compatibility renderer stutter more?

It doesn't get Godot 4.4's ubershaders, so first-use shader hitches matter more there, and older Android GPUs have tighter limits. Draw effects once behind the loading screen and keep shaders simple. Web and many Android exports use it.

### My game uses C#. Does garbage collection cause stutter?

It can: the .NET garbage collector pauses the game when it runs. Avoid allocating every frame in `_Process` (new lists, LINQ, string building) and reuse objects. GDScript uses reference counting, so it doesn't have collection pauses.

## Sources

- [Godot docs: Physics interpolation](https://docs.godotengine.org/en/stable/tutorials/physics/interpolation/index.html)
- [Godot docs: Reducing stutter from shader (pipeline) compilations](https://docs.godotengine.org/en/stable/tutorials/performance/pipeline_compilations.html)
- [Godot docs: Background loading](https://docs.godotengine.org/en/stable/tutorials/io/background_loading.html)
- [Godot docs: The Profiler](https://docs.godotengine.org/en/stable/tutorials/scripting/debug/the_profiler.html)
- [Godot docs: Optimization using MultiMeshes](https://docs.godotengine.org/en/stable/tutorials/performance/using_multimesh.html)
- [Godot docs: Performance monitors](https://docs.godotengine.org/en/stable/classes/class_performance.html)
