Design patterns in Godot

If you have worked with Godot for a while, you have probably used design patterns without knowing it. When you connect a signal to react to an event, you are using the Observer pattern. When you add an AutoLoad to share data across scenes, you are using something along the lines of the Singleton pattern. Godot builds several of these patterns directly into the engine.

Design patterns are names we put on solutions to problems that come up repeatedly in programming. Developers were already using these solutions before anyone gave them names. The names help us learn these techniques more deliberately and talk about them with teammates without long explanations.

In this guide, I want to focus on the patterns that are actually relevant when using Godot and GDScript. You will learn which ones Godot already handles for you, when it makes sense to reach for a pattern yourself, and when a simpler solution is the better choice.

Design patterns are not a recipe book. At least, that's not how I recommend thinking about them.

As you gain experience, you stop thinking "I should apply the Observer pattern here" and start thinking instead "I need these two entities to react to each other without depending on either being always present." The pattern becomes one of many techniques you have internalized and use naturally when it makes sense.

Some patterns solve real problems: flyweight helps you avoid duplicating data in memory (resources in Godot), and finite state machines give you an accessible way to structure complex behaviors. But others, like fluent interfaces, are more about coding style.

You do not need to learn every pattern out there, and like many things in programming, you also don't want to treat them as gospel.

Nathan

Founder and teacher at GDQuest

When to actually use a pattern

Pretty much every pattern you add to your code comes at a cost: you add more abstraction, and that means more code to read, more things to keep in mind when you come back to it months later, and more work when something needs to change.

The benefits have to be at least worth that.

A trap that many developers fall into is reaching for patterns too early, before the problem is clear enough to know what solution it actually needs. When you first learn about state machines, for example, it is tempting to use them everywhere: characters, menus, chests, doors... The pattern feels miraculous when you're starting out and struggling with keeping variables under control. It can start to feel like the right tool for anything that has more than one state!

But a chest that can only be open or closed does not need a state machine; a boolean is really all you need. A menu where you just navigate into sub-menus does not need a state machine either, an array that tracks your navigation history is simpler and may be all you need.

It is also worth noting that "state machine" can mean different things. The simplest form is just an enum and a variable that tracks the current state, and that is often enough. What tends to get expensive is the version where each state is its own class or object with its own logic encapsulated inside it. That version gives you a lot of flexibility, but it also adds a lot of code, and it is the one you want to think twice about before using.

A state machine starts to make sense when you know the object has many distinct states, say more than three or four, and when you expect to keep going back to add or change behaviors as you develop the game. A player character or a complex enemy are good candidates because you will tweak them constantly. A chest is not.

The same thinking applies to patterns in general. Before using one, ask yourself if the problem is actually complex enough to need it, and whether this part of the code is really going to change that much.

It's also worth being honest with yourself about whether you are using the pattern because it fits, or because you learned it and want to use it somewhere. That's a normal thing to do, but it is also how you end up with code that is harder to work with than it needs to be.

My own approach is to write the simplest code that solves the problem first. Then if the code starts getting hard to manage, or I find myself writing the same kind of structure in multiple places, I look at whether a pattern or abstraction really helps.

A lot of game code does not quite need much more than a straightforward scripts. Especially when using engines that do a lot of heavy lifting for you like Godot. When I try to plan for future needs too early, I almost always end up building for problems that never come, or worse, writing code that gets cut from the final program. I have done this more times than I would like to admit!

Nathan

Founder and teacher at GDQuest

Patterns Godot already handles for you

Godot has several well-known patterns built directly into the engine, ready for you to use in your games. Here's a runthrough of some of them, which you don't need to re-implement yourself. Plus, if you're already experienced with programming, this will help you connect Godot's features to patterns you already know.

Observer: This pattern lets one object react to events in another object without the two being directly connected. In Godot, signals are the implementation of this. When you emit a signal, any number of other objects can listen and react without the emitting node knowing anything about them. You can delete objects anytime and signals disconnect automatically.

Singleton: A singleton is a shared instance of something you can access from anywhere in your code. In Godot, "autoloads" work this way in practice: you register a scene or script as an autoload and Godot makes a single, globally accessible instance out of it. It is worth noting that autoloads are not true singletons in the strict sense, as nothing prevents you from creating another instance of the same script. But in game development, what we usually care about is having one globally accessible instance, and this feature gives you exactly that. Godot also has its own built-in singletons like Input, Time, and AudioServer that work the same way.

Prototype: This pattern is about creating new entities by cloning an existing one rather than building them from scratch. In Godot, this is exactly what scenes do! When you call instantiate() on a PackedScene resource, the scene is the prototype and each instance is a clone of it.

Flyweight: This pattern is about sharing data between many objects to avoid duplicating it in memory. In Godot, resources (textures, scripts, materials, audio files, collision shapes, etc.) work this way by default. When multiple nodes reference the same resource, they all point to the same data in memory rather than each holding their own copy. The engine even has an extra optimization where trying to load the same resource from multiple places in your codebase will automatically find an already loaded copy in memory if possible.

To learn about more programming patterns you may find useful in games and game engines, Robert Nystrom's Game Programming Patterns is well worth bookmarking. Some patterns in there like dirty flags, spatial partitioning, and more are used in Godot's own codebase, and it's always worth learning and practicing different programming techniques to get a better sense for how things work.

Nathan

Founder and teacher at GDQuest
Do I need object pooling in Godot?

Pooling is the process of pre-instantiating a set of objects, storing them in an array, and reusing those instances over and over instead of creating and destroying objects on the fly (which is what happens when you instantiate a scene and call queue_free() on nodes).

The reason pooling exists is that allocating memory dynamically (like when creating and freeing nodes) has a cost. Every time you instantiate a new object, the program has to find and reserve a chunk of memory for it. When you destroy that object, that memory has to be freed. If you are doing this hundreds of times per second, the cost of creating new nodes adds up. Pooling avoids that cost by doing all the allocations upfront and then reusing the same memory.

In languages like C#, this matters even more because of the garbage collector. In C#, memory is not freed immediately when you destroy an object. Instead, the language runs a garbage collector at intervals you cannot fully control, and that process can take long enough to freeze your game for a few frames. Pooling avoids triggering the garbage collector by never discarding data in the first place.

GDScript uses reference-counted memory management instead. Objects are freed as soon as nothing refers to them anymore, which is more predictable and does not cause garbage collection pauses. The allocation cost still exists, but it is less likely to be a problem in practice.

There are cases where it makes sense to use pools in GDScript. For example, a shooter game where you need to instantiate many projectiles every frame. In that case, pre-instantiating a large pool of instances at the start of the game may help improve performance.

For most Godot games though, the cost of instantiating and freeing nodes will be a very small part of your overall performance load. Pooling adds a little more bookkeeping as it requires you to manually reset each object's state when you reuse it. You also have to correctly estimate the pool size to avoid storing too many unused instances.

Here is what a simple pool looks like in GDScript, using a cycling index to always hand out the next available instance:

## Pool of reusable bullets
class_name BulletPool

## Number of bullets to pre-instantiate
const POOL_SIZE := 50
const BulletScene := preload("res://path/to/Bullet.tscn")

var bullets := []
var _last_index := -1


func initialize() -> void:
	for i in POOL_SIZE:
		bullets.append(BulletScene.instantiate())


func get_bullet() -> Bullet:
	_last_index = wrapi(_last_index + 1, 0, POOL_SIZE)
	return bullets[_last_index]

The pool pre-instantiates all bullets and keeps a reference to each so they are never freed. Every time you need a bullet, you call get_bullet(), which returns the next instance in the cycle. Make sure the pool is large enough that each bullet has time to finish its job before the index comes back around to reuse it.

Should I use an Entity-Component System (ECS) in Godot?

The Entity-Component System (ECS) is an architectural pattern where you build game objects, called entities, by attaching small specific components to them. Each component handles one specific piece of behavior: one for collision, one for movement, one for health, one for audio, and so on. The entity itself is just a container.

There are two reasons people reach for ECS: performance and flexibility.

On performance: ECS gives you performance benefits only when you implement it in a compiled language like C or C++, where you have direct control over how data is stored and laid out in memory. The idea is that you group related component data tightly together so the CPU can process it as efficiently as possible. This can make a lot of sense for a general-purpose game engine that has to handle any kind of game without knowing in advance how the engine will be used. Especially when the engine is really complex or you don't have access to its source code.

In GDScript, you do not have that kind of control over memory layout. An ECS written in GDScript gives you none of the performance benefits (just the added complexity). Even in C++, this structure makes the most sense when you need to deliver a framework that supports creating all kinds of games. For your own Godot games, you can instead write specific code tailored to your project when you have a performance bottleneck.

ECS is also not the only way to write performance-oriented code. Godot uses different techniques within the engine core for that purpose.

On flexibility: ECS does give you a way to build your entire game around composition, but actually so does Godot's node system. You can think of nodes as components in a scene: you compose your game entities by adding nodes to them, each with a specific purpose. Sprite node to draw a sprite, physics body for movement and collision, area for detection, etc.

Replacing Godot's node system with your own entities and components means that you end up building a parallel structure on top of the engine, which goes against the grain of Godot's design.

In summary, if you are coming from Unity and wondering whether you need ECS in Godot: you don't. If you have very specific needs like a heavy simulation, you'll probably want to write code tailored to this project. Godot makes it easy to do.

updates / code patches

Learn common game programming patterns in practice!
In Module 13 of Learn 2D Gamedev From Zero, you code a polished 2D side-scroller character using an enum-based state machine. In Module 4 of Learn 3D Gamedev From Zero, you build complex AI state machines with reusable states for enemy behaviors. Both courses cover some of the most useful game programming patterns you'll use in Godot in a hands-on way.

Nathan

Founder and teacher at GDQuest
Check out GDSchool
Become an Indie Gamedev with GDQuest!

Don't stop here. Step-by-step tutorials are fun but they only take you so far.

Try one of our proven study programs to become an independent Gamedev truly capable of realizing the games you’ve always wanted to make.

Nathan

Founder and teacher at GDQuest
  • Starter Kit
  • Learn Gamedev from Zero
Check out GDSchool

You're welcome in our little community

Get help from peers and pros on GDQuest's Discord server!

20,000 membersJoin Server

Contribute to GDQuest's Free Library

There are multiple ways you can join our effort to create free and open source gamedev resources that are accessible to everyone!

Site in BETA!found a bug?