OPTIMIZATION

Object Pooling

What is it?

Object Pooling is a design pattern where objects that are expensive to create and destroy are prepared in advance and stored in a pool. When an object is needed, it is taken from the pool instead of being newly created.

In Unity, this is commonly used for objects like bullets, enemies, particle effects, damage texts, shell casings, or UI popups.

The basic idea is simple: instead of throwing objects away, reuse them.

When is it used?

Object Pooling is useful when the same type of object is created and removed many times during gameplay.

It is a good choice when:

  • Many objects are spawned and removed in a short time.
  • Instantiate and Destroy calls cause frame drops or freezes.
  • The game targets mobile or lower-end devices.
  • Repeated objects such as bullets, enemies, particles, or popups are used often.
  • Resetting an object is cheaper than creating it from scratch.

However, Object Pooling is not always necessary. For objects that appear only once or a few times in a scene, pooling may add unnecessary complexity.

Animation

Pool: —
Active: —
▶ ▶ ▶conveyor beltObjectPoolGet() · Return()GunPool.Get()ACTIVEReturn()

Code

Assets / Scripts / Optimization / ObjectPoolObjectPool.cs
READ ONLY
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
using System.Collections.Generic;
using UnityEngine;

namespace Patterns.Optimization.ObjectPool
{
    public class ObjectPool : MonoBehaviour
    {
        [SerializeField] private GameObject _prefab;
        [SerializeField] private int _initialPoolSize = 20;

        private readonly Queue<GameObject> _pool = new Queue<GameObject>();

        private void Awake()
        {
            for (int i = 0; i < _initialPoolSize; i++)
            {
                GameObject obj = Instantiate(_prefab);
                obj.SetActive(false);
                _pool.Enqueue(obj);
            }
        }

        public GameObject Get()
        {
            if (_pool.Count > 0)
            {
                GameObject obj = _pool.Dequeue();
                obj.SetActive(true);
                return obj;
            }

            GameObject newObj = Instantiate(_prefab);
            newObj.SetActive(true);
            return newObj;
        }

        public void Return(GameObject obj)
        {
            obj.SetActive(false);
            _pool.Enqueue(obj);
        }
    }
}

Advantages and Disadvantages

• Advantages:

  • Reduces the use of Instantiate and Destroy.
  • Lowers Garbage Collector pressure.
  • Helps prevent frame drops and runtime stutters.
  • Makes performance more stable.
  • Works well for mobile and performance-sensitive games.
  • Reuses object components instead of allocating new ones repeatedly.

• Disadvantages:

  • Large pools can waste memory.
  • Small pools may still need to create new objects during gameplay.
  • Objects must be reset correctly after every use.
  • The code is more complex than using Instantiate and Destroy.
  • Scene changes and object references need extra attention.

Tips

In Unity, one of the best practices is to prepare the pool before gameplay starts. This is often called pre-warming.

For Unity 2021 and newer, UnityEngine.Pool.ObjectPool<T> can be used instead of writing a full pooling system manually.

Useful points to keep in mind:

  • Decide the pool size by testing with the Unity Profiler.
  • Always reset the object before returning it to the pool.
  • Clear values such as velocity, health, effects, coroutines, and event subscriptions.
  • Use a separate pool for each prefab type.
  • Decide what should happen when the pool is empty.
  • Be careful with pools during scene changes.
  • Use pre-warming to move creation cost to the loading phase.

A clean approach is to create a separate pool for each repeated object type, such as bullets, enemies, or effects.