Object Pool Pattern
Summary
The Object Pool pattern keeps a stock of reusable objects ready in memory instead of repeatedly creating and destroying them at runtime. When gameplay needs an object, code asks the pool for an inactive instance, activates it, and uses it. When the object is no longer needed, it is disabled and returned to the pool rather than destroyed.
In Unity, this pattern is most helpful for short-lived or high-frequency objects such as bullets, impact effects, particles, floating damage numbers, and sometimes enemies. Repeated Instantiate and Destroy calls can create CPU overhead and trigger garbage-collection spikes; pooling smooths this out by paying the setup cost earlier and reusing what already exists (Unity Technologies, Level Up Your Code with Game Programming Patterns, see source-unity-game-programming-patterns).
Implementation
Simple custom pool
using System.Collections.Generic;
using UnityEngine;
public class ProjectilePool : MonoBehaviour
{
[SerializeField] private Projectile projectilePrefab;
[SerializeField] private int initialSize = 20;
private readonly Stack<Projectile> inactive = new();
private void Awake()
{
for (int i = 0; i < initialSize; i++)
{
Projectile projectile = Instantiate(projectilePrefab, transform);
projectile.gameObject.SetActive(false);
projectile.SetPool(this);
inactive.Push(projectile);
}
}
public Projectile Get()
{
Projectile projectile = inactive.Count > 0
? inactive.Pop()
: Instantiate(projectilePrefab, transform);
projectile.SetPool(this);
projectile.gameObject.SetActive(true);
return projectile;
}
public void Release(Projectile projectile)
{
projectile.gameObject.SetActive(false);
inactive.Push(projectile);
}
}using UnityEngine;
public class Projectile : MonoBehaviour
{
private ProjectilePool pool;
public void SetPool(ProjectilePool ownerPool)
{
pool = ownerPool;
}
public void Fire(Vector3 position, Vector3 direction, float speed)
{
transform.position = position;
GetComponent<Rigidbody>().linearVelocity = direction * speed;
}
private void OnBecameInvisible()
{
pool.Release(this);
}
}The important idea is not the exact implementation. The pattern is the lifecycle:
- Pre-create reusable objects.
- Hand them out on demand.
- Reset and return them when finished.
Unity’s built-in pool API
Unity 2021+ includes UnityEngine.Pool.ObjectPool<T>, which removes a lot of the boilerplate:
using UnityEngine;
using UnityEngine.Pool;
public class BuiltInProjectilePool : MonoBehaviour
{
[SerializeField] private Projectile projectilePrefab;
private ObjectPool<Projectile> pool;
private void Awake()
{
pool = new ObjectPool<Projectile>(
createFunc: CreateProjectile,
actionOnGet: p => p.gameObject.SetActive(true),
actionOnRelease: p => p.gameObject.SetActive(false),
actionOnDestroy: p => Destroy(p.gameObject),
collectionCheck: false,
defaultCapacity: 20,
maxSize: 100
);
}
private Projectile CreateProjectile()
{
Projectile projectile = Instantiate(projectilePrefab, transform);
projectile.SetBuiltInPool(pool);
return projectile;
}
public Projectile Get() => pool.Get();
}This is often the best default choice in modern Unity unless you need a very custom pooling system.
Trade-offs
When to use:
- Objects are spawned and removed frequently.
- Runtime hitching or garbage-collection spikes are noticeable.
- Objects have predictable reuse behaviour and can be reset cleanly.
- You want to centralise lifetime management for bullets, VFX, or temporary props.
When not to:
- Objects are rare or long-lived, so the pooling overhead is unnecessary.
- Resetting object state safely is more complex than simple re-instantiation.
- Memory is extremely tight and preallocating many inactive instances would be wasteful.
Main trade-off: pooling improves runtime smoothness, but it can increase memory usage and adds reset logic. A pooled object must return to a clean state each time, or subtle bugs accumulate.
Examples
- Projectile systems: bullets, arrows, missiles, and hitscan tracers
- VFX bursts: impact sparks, muzzle flashes, smoke puffs, debris
- UI feedback: floating damage numbers or pickup popups
- Enemy waves: lightweight enemies or hazards that enter and leave play repeatedly
Related
- factory-pattern — factories and pools pair naturally when centralising how spawned objects are created or reused
- particle-systems-design — particle-heavy effects often benefit from pooling
- unity-profiler — use the profiler to verify whether allocation spikes justify pooling
- source-unity-game-programming-patterns — primary source