Tutorial2 min read

Object pooling in Unity, the built-in way

Unity has shipped a generic object pool since 2021. How to use UnityEngine.Pool properly: prewarming, resetting state, and the one mistake that corrupts a pool.

Every mobile game I’ve shipped has had the same frame-time spike: a burst of bullets, coins or particles, each one created with Instantiate on the spot and thrown away with Destroy a second later. Creating objects is slow, and the garbage they leave behind comes back later as a hitch when the collector runs.

Object pooling fixes it by reusing instances instead of creating new ones. You don’t need a plugin for that any more: Unity 2021.1 added UnityEngine.Pool, and it covers almost everything a game needs.

The minimal pool

C#
using UnityEngine;
using UnityEngine.Pool;

public class CoinSpawner : MonoBehaviour
{
    [SerializeField] Coin prefab;
    ObjectPool<Coin> pool;

    void Awake()
    {
        pool = new ObjectPool<Coin>(
            createFunc: () => Instantiate(prefab),
            actionOnGet: coin => coin.gameObject.SetActive(true),
            actionOnRelease: coin => coin.gameObject.SetActive(false),
            actionOnDestroy: coin => Destroy(coin.gameObject),
            collectionCheck: true,
            defaultCapacity: 32,
            maxSize: 256);
    }

    public void Spawn(Vector3 position)
    {
        Coin coin = pool.Get();
        coin.transform.position = position;
        coin.Init(pool);
    }
}

Get() hands you an inactive instance if one is waiting and only calls createFunc when the pool is empty. Release() puts an instance back. maxSize caps how many inactive objects the pool keeps; anything released beyond that is destroyed instead of stored, so a one-off explosion of 2,000 coins doesn’t pin 2,000 objects in memory forever.

Let the object return itself

The spawner shouldn’t have to track every coin. Give the pooled object its pool and let it release itself when it’s done:

C#
public class Coin : MonoBehaviour
{
    IObjectPool<Coin> pool;
    float life;

    public void Init(IObjectPool<Coin> owner)
    {
        pool = owner;
        life = 3f;
    }

    void Update()
    {
        life -= Time.deltaTime;
        if (life <= 0f) pool.Release(this);
    }
}

Depending on IObjectPool<Coin> rather than the concrete class also means you can swap in a different pool, or a fake one in tests, without touching Coin.

Reset state on the way out

A pooled object comes back exactly as you left it: velocity, trail, colour, the lot. Reset it when it’s released, or in its Init, so a recycled coin can never inherit the last one’s state. Physics is the classic trap — zero the Rigidbody’s linearVelocity (velocity before Unity 6) and angularVelocity, or the coin flies off the moment it reappears. Call Clear() on any TrailRenderer too, or it draws a streak from wherever the object last died.

Prewarm before the first burst

A pool that starts empty still creates objects during your first big wave. Fill it behind a loading screen instead:

C#
var warm = new List<Coin>();
for (int i = 0; i < 32; i++) warm.Add(pool.Get());
foreach (Coin coin in warm) pool.Release(coin);

The mistake that corrupts a pool

Releasing the same instance twice breaks a pool quietly: two later Get() calls return the same object, and one of them gets yanked away mid-flight. With collectionCheck: true, Unity throws the moment it happens, which turns a baffling bug into a stack trace. Leave it on while you develop; it costs a lookup per release, so switching it off for release builds is reasonable once the game is stable.

  • C#
  • Performance
  • Mobile

Written by

Logical Programmer

Solo indie developer. I build games in Unity, ship them on Google Play, and document all of it.

Built with this package

Utilities

1,240 downloads

Unity Starter Utilities

The helper scripts I paste into every new project — singletons, object pooling, timers, extensions.

Free

Details

Free download

Free downloads open soon

Email me and I’ll send you Unity Starter Utilities directly.

Unity Starter UtilitiesFree

Keep reading

All articles
  • Tutorial2 min read

    Save files that survive your next update

    Every update that changes your save format can wipe a player’s progress. A version number, one migration step per version and a safe write stop it from ever happening.

    Read

  • Tutorial2 min read

    Enemy AI you can read at a glance

    A finite state machine in plain C#: patrol, chase and search as small classes, with every transition in one place. No behaviour tree required.

    Read

  • Game design2 min read

    Why your mobile joystick feels wrong

    Three numbers decide whether an on-screen joystick feels sharp or mushy: the dead zone, the travel radius and where the stick’s centre sits. How to tune each one.

    Read