All posts
UnityGuide

Object Pooling in Unity: How to Stop Instantiate From Wrecking Your Frame Rate

July 1, 2026 DevLoot

The stutter nobody warns you about

You build a shooter. It runs great in the editor. Then you hold down fire, twenty bullets a second start flying, and every few frames the whole thing hitches like it hit a pothole. That hitch usually is not your GPU. It is the garbage collector cleaning up after all the objects you spawned and threw away.

Every time you call Instantiate and later Destroy, Unity allocates managed memory and eventually has to collect it. Do that a few hundred times a second and the collector fires mid gameplay. In the Profiler it shows up as bright magenta GC.Alloc samples and a tall spike in the frame time graph. Players feel it as a micro freeze. Object pooling is how you make that spike go away.

What pooling actually does

The idea is boring, and that is the point. Instead of creating an object when you need it and destroying it when you are done, you keep a stash of pre-made objects. When you need one, you pull it off the stash and switch it on. When you are done, you switch it off and put it back. Nothing gets created, nothing gets destroyed, so there is nothing for the collector to clean up.

Pooling does not make spawning faster. It makes spawning stop producing garbage, and garbage is what stutters your game.

Think of it like a coffee shop that washes and reuses mugs instead of buying a brand new mug for every customer and tossing it in the bin. Same mugs, many customers, no pile of trash out back.

Use the built-in pool, not a homemade one

For years everyone wrote their own pool manager with a List and a pile of SetActive calls. You do not need to anymore. Since Unity 2021 LTS, and still current in Unity 6, there is a proper generic pool sitting in the UnityEngine.Pool namespace called ObjectPool<T>. It is a stack based pool that takes a handful of callbacks, so you control exactly what happens at each stage of an object's life.

using UnityEngine;
using UnityEngine.Pool;

public class BulletSpawner : MonoBehaviour
{
    [SerializeField] private Bullet bulletPrefab;
    private ObjectPool<Bullet> pool;

    void Awake()
    {
        pool = new ObjectPool<Bullet>(
            createFunc: () => Instantiate(bulletPrefab),
            actionOnGet: b => b.gameObject.SetActive(true),
            actionOnRelease: b => b.gameObject.SetActive(false),
            actionOnDestroy: b => Destroy(b.gameObject),
            collectionCheck: true,
            defaultCapacity: 50,
            maxSize: 200);
    }

    public Bullet Fire()
    {
        Bullet b = pool.Get();
        b.SetPool(pool);   // so the bullet can release itself on impact
        return b;
    }
}

The callbacks are where the bugs live

createFunc runs only when the pool is empty and needs a fresh instance. actionOnGet runs every time you pull one out, so that is where you turn it on and reset it. actionOnRelease runs when you hand one back, so that is where you turn it off. actionOnDestroy runs when the pool is over its maxSize and decides to actually throw an object away.

The single most common pooling bug is a dirty object. A bullet you fired earlier still has velocity, a half finished trail, or a ticking timer. You pull it from the pool, it wakes up with all that stale state, and now it flies off in the wrong direction or explodes on frame one. Reset everything in actionOnGet: position, rotation, velocity, particle trails, health, timers. Treat every object coming out of the pool as if it just woke up from a nap and remembers nothing.

Return objects, do not destroy them

When a bullet hits a wall, call pool.Release(bullet) instead of Destroy. That is the whole trick. Keep collectionCheck on while you develop, because it throws an error if you accidentally release the same object twice, which is a nasty bug to track down later. You can turn it off in release builds for a small speed win.

What is actually worth pooling

Do not pool everything. Pooling adds real complexity, because now every object has a lifecycle you manage by hand. Save it for the stuff you spawn constantly: bullets, waves of enemies, explosion and hit effects, floating damage numbers, footstep decals. A boss you spawn once per level is not worth a pool.

The honest rule is to profile first. Open the Profiler, watch the GC.Alloc track while you play the messy part of your game, and if you see fat allocations and frame spikes lining up with spawning, pool that thing. If you do not see them, leave it alone. Guessing at optimization is how you spend a weekend making clean code ugly for zero frame rate gain. There is more on hunting those bottlenecks in our guide on how to optimize your Unity game for better performance.

Prewarm so the first shot is smooth

An empty pool still has to build its first batch of objects, and if that happens the instant combat starts, you just moved your hitch to the worst possible moment. Fill the pool during a loading screen instead. Get a batch of objects and immediately release them, or set defaultCapacity to roughly the peak number you expect on screen at once. Now the allocations happen while the player stares at a spinner, not while they dodge.

When the built-in pool is not enough

For most games ObjectPool<T> is all you will ever need. If you are pushing tens of thousands of active objects, like a proper bullet hell curtain, the stack based approach can start to show its edges, and people reach for community libraries. uPools adds async pooling and a component friendly API, and eilume's Object Pool uses a sparse set so operations stay fast even at very large pool sizes. Both are MIT licensed and drop in cleanly.

eilume Object Pool, an open source sparse set object pooling library for Unity

The eilume Object Pool library, a sparse set pool for very large object counts. Credit: eilume/unity-object-pool on GitHub.

One more practical note. If you buy a projectile system, enemy pack, or VFX bundle off a marketplace like DevLoot, open it up and check whether it already pools its spawns before you bolt your own pool on top. Good packs handle this for you, and doubling up on pools is its own kind of mess. The same thinking applies when you wire up spawners for a placement or building system, which we walked through in object placement in Unity.

The short version

Spawning and destroying objects at speed produces garbage, garbage triggers the collector, and the collector stutters your frame rate. Pool the things you spawn constantly, use the built-in ObjectPool<T>, reset state on the way out, release instead of destroy, and prewarm during loading. Do that and the hitching that made your game feel cheap just quietly goes away.

Object Pooling in Unity: How to Stop Instantiate From Wrecking Your Frame Rate · DevLoot