Object Pooling for a Mobile Game: Implementation and Optimization

Frame drops during combat peaks and stutters during mass effects hurt player retention. We implement object pooling in Unity turnkey: we identify load sources, configure object reuse, and validate the result with measurements. Your game maintains a steady pace on low-end devices.

Development and support of all types of mobile applications:

Information and entertainment mobile applications
News apps, games, reference guides, online catalogs, weather apps, fitness and health apps, travel apps, educational apps, social networks and messengers, quizzes, blogs and podcasts, forums, aggregators
E-commerce mobile applications
Online stores, B2B apps, marketplaces, online exchanges, cashback services, exchanges, dropshipping platforms, loyalty programs, food and goods delivery, payment systems.
Business process management mobile applications
CRM systems, ERP systems, project management, sales team tools, financial management, production management, logistics and delivery management, HR management, data monitoring systems
Electronic services mobile applications
Classified ads platforms, online schools, online cinemas, electronic service platforms, cashback platforms, video hosting, thematic portals, online booking and scheduling platforms, online trading platforms

These are just some of the types of mobile applications we work with, and each of them may have its own specific features and functionality, tailored to the specific needs and goals of the client.

Our competencies:

Frequently Asked Questions

Latest works

  • Development of a mobile application for FEEDME
    Development of a mobile application for FEEDME
    971
  • Development of a mobile application for XOOMER
    Development of a mobile application for XOOMER
    869
  • Development of a mobile application for RHL
    Development of a mobile application for RHL
    1289
  • Development of a mobile application for ZIPPY
    Development of a mobile application for ZIPPY
    1143
  • Development of a mobile application for Affhome
    Development of a mobile application for Affhome
    1076
  • Development of a mobile application for the FLAVORS company
    Development of a mobile application for the FLAVORS company
    677

Why an object pool in Unity solves the problem of frame drops

Constant Instantiate and Destroy calls in the game loop are one of the most expensive habits in a project. Each call creates a new managed object on the heap, and destruction leaves behind garbage that the collector must eventually clean up.

While there are only dozens of objects, this goes unnoticed; with bullets, particles, enemies, and pop-up hints, the count runs into hundreds per second.

The problem manifests not in average performance, but in frame time spikes. Garbage collection triggers not when it's convenient, but when the heap fills up — and it halts the thread for a duration that depends on the volume of live objects.

The player sees this as a stutter: controls "stick," animation jerks, a hit doesn't register. On weak devices with little memory, the collector is invoked more frequently, and fps drops become regular.

An object pool flips the logic: objects are created once at scene start and are then not destroyed but reused. When a bullet hits a target, it isn't deleted but returned to the pool and resets its state — position, velocity, timers, references to effects. On the next shot, a ready-made instance is taken, and not a single new object appears on the heap.

The practical effect is a smooth frame instead of a sawtooth graph. The collector stops triggering in the heat of battle because no garbage is produced; frame time stays within a predictable corridor rather than spiking.

An object pool in Unity is especially important for scenes with a dense stream of short-lived entities: bullets, shell casings, sparks, floating damage numbers, enemies from waves.

There's a second benefit as well — managed memory. The pool size is set in advance and grows according to a clear rule, rather than chaotically under load. This allows maintaining a stable memory budget on the device and avoids hitting the limit at the worst possible moment. Let's break down how to design such a pool so it doesn't turn into a source of new problems.

What object pooling optimization in a mobile game delivers

Pooling removes the main source of frame instability — constant allocations and deallocations of objects at runtime.

When projectiles, enemies, and effects are reused from a pre-created set, the processor stops spending time reserving memory in the heat of battle, and frame time stays even instead of a sawtooth graph.

It is on weak devices, where the headroom for CPU load is minimal, that this difference is most visible: the game does not "stutter" in mass scenes.

The second effect is predictable memory consumption. The pool fixes the upper bound of live objects, so the garbage collector does not get spikes and does not run at the most inopportune moment.

Long pauses, because of which the frame drops and controls "stick", become a thing of the past — only short background passes remain, unnoticeable to the player.

A separate benefit is a controllable class of failures. When the pool limit is exhausted, behavior is defined explicitly: the object is reused by priority, queued, or disabled.

This is better than uncontrolled memory growth and a crash from resource shortage, which can neither be predicted nor reproduced on a test bench.

Finally, recovery time after a failure is reduced. The pool can be reset to its initial state with a single operation, returning all objects to the set — the scene returns to a working state without a full level reload.

For teams, this also means stable fps across the entire device fleet, not just on top-tier models where problems are masked by excess power.

Approaches to pool organization: comparing the options

The choice between a manual pool, built-in engine tools, and third-party libraries is a choice between control, speed of implementation, and maintenance burden. Below is a comparison by applicability and limitations, not "which is better in general."

Option When it fits Limitations
Manual pool Heterogeneous objects, complex reuse logic, requirements for deterministic behavior You have to handle object return, state reset, and protection against double return yourself
Built-in pool Uniform frequently created objects: projectiles, effects, tooltips Limited policy flexibility; some scenarios still require custom plumbing
Third-party libraries Quick start, typical scenarios, ready-made extension policies Dependency on external code, its own lifecycle, risk of conflict with engine updates

Lifecycle management

The key difference is who is responsible for returning an object to the pool. In the manual option, the calling code does this, and this is where typical defects appear: an object was returned twice, returned to the wrong owner, or not returned at all after an exception.

Built-in and library solutions remove part of this burden, but they require an explicit state reset on checkout — otherwise the object arrives with leftovers from previous use.

Limitations at scale

A manual pool is predictable but more expensive to maintain as the number of object types grows. A built-in one is fast as long as the scenario is uniform. A third-party one is convenient at the start but adds an external dependency to the project's lifecycle.

In practice, they are often combined: a built-in pool for mass uniform objects, a manual one where a custom reset and accounting policy is needed.

Describe which objects spawn en masse in your game and how often — we'll come back with an estimate and a plan for each option.

How we implement an object pool: stages of work

Work starts not with code, but with measurement: until there are numbers on allocations, any optimization is guesswork.

We capture a profile on real game scenarios — combat, level transitions, mass enemy spawning — and find the sources of garbage that triggers collection and drops frames.

  1. Allocation audit. We run a memory profiler on target devices, record garbage collection frequency, consumption peaks, and the frames where the drop is noticeable to the player.

    The output is a list of candidate objects: bullets, enemies, particle effects, pop-up hints, audio sources.

  2. Pool scheme design. We determine what to reuse and what is cheaper to create anew. We set the initial pool size, the growth limit, and the behavior when exhausted: expand, wait for release, or discard the request. Here we also decide how an object returns to the pool — manually or automatically by event.

  3. Implementation and integration. We write the pool for the project's architecture, not on top of it: a creation factory, resetting object state before issuing, a single return point.

    We remove direct creation and deletion calls throughout the code so that objects don't "leak" past the pool.

  4. Tuning for scenarios. We select pool sizes separately for each type of object and load level, and configure warm-up at the loading stage so that the first burst of activity doesn't cause an avalanche of object creation.

  5. Verifying the result. We repeat the measurements on the same scenarios and devices: we compare garbage collection frequency, frame stability, and memory consumption before and after. We separately check edge cases — pool overflow, simultaneous return of objects, level restart.

  6. Handover and documentation. We deliver the configured system, the pool scheme with parameters, instructions for adding new types of objects, and a verification checklist for changes in the game.

The process is iterative: if after implementation the profile shows a new source of allocations, we return to the audit step. Describe your game and current frame drops — we'll come back with an estimate and a work plan.

What the client receives after optimizing an object pool

Object pooling is not a one-time code fix, but part of the runtime architecture that the team continues to maintain.

Therefore, after optimization, the client receives not "it got faster," but a set of artifacts by which the system can be developed and handed over without losing context.

  • Pool configurations — parameters extracted from the code: initial and maximum size, growth step, prefill policy at scene start, binding to object types. Values are changed without rebuilding, for different scenes and load modes.
  • Lifecycle diagrams — state diagrams of an object: creation, checkout from the pool, use, return, state reset, destruction on overflow. It is visible where an object can "leak" and fail to return.
  • Access policies — rules for who and when takes an object from the pool, what happens when free objects are exhausted, how a repeated return and access to an already released object are handled.
  • State reset regulations — what exactly is zeroed out on return: transforms, materials, event subscriptions, timers, references to other objects. Without this, the pool hands out "dirty" instances.
  • Diagnostic tools — counters of active and free objects, peak values, a pool miss log for when a request goes to creating a new instance instead of reuse.
  • Documentation — a description of the pool architecture, extension points for new object types, known limitations, and the procedure for adding entities to the system.
  • Maintenance regulations — how to change pool sizes as load grows, how to check for the absence of leaks before release, which metrics to look at first.

Such a kit removes dependence on a specific developer: the pool remains manageable, rather than "magic" that people are afraid to touch. Describe your task — we will come back with an estimate and a work plan.

Case study: how an object pool eliminated frame drops in a mobile game

The project is a casual arcade game with waves of enemies and projectiles that were created and destroyed by the dozens per second.

On mid-range devices this produced regular frame-time spikes: the garbage collector ran several times per battle and "ate" frames in batches, and on low-end models the game visibly stuttered precisely during the most dynamic moments.

What was changed

Projectiles, enemies, hit effects, and floating damage numbers were moved to an object pool. Instances are created once at level load, and from then on are only taken from the pool and returned back with a full state reset — position, velocity, timers, component references.

A leak was also closed separately: an object returned to the pool unsubscribes from events and stops ticking in the frame update.

Metrics before and after

Metric Before pooling After pooling
Garbage collector spikes per battle 8–12 1–2
Frame-time drops regular, up to 3× the norm isolated, within the norm
Share of frames below the target threshold noticeable close to zero
Stability on low-end devices stuttering in battle smooth performance

The key effect is not the average frame, but specifically the tail of the distribution: the rare but very long frames that the player perceives as "freezing" disappeared.

They were eliminated not by the pool itself, but by the fact that it removed the memory-management load from Unity in the hot loop.

We also separately verified that the pool does not grow indefinitely: we set an upper bound for each type and an expansion policy for when it is exhausted. If an object returns to the pool with an unclosed state, it is caught in a test run rather than in release.

We apply this approach in other projects as well: first we profile the real battle, then we optimize what actually heats up the frame.

How is an object pool different from regular instantiation?

Instantiation is not a single operation but a chain: allocating a block in the managed heap, initializing fields, registering in engine structures.

When this happens dozens of times per second — for bullets, particles, floating damage numbers — the heap becomes fragmented, and the garbage collector takes the stage at the worst possible moment. A pool flips the logic: objects are created once at level start and thereafter only switch state.

The difference shows up in the nature of the load. With regular instantiation, memory allocation grows along with the pace of the game: the more intense the combat, the more often the collector stalls the frame, and the dips peak exactly during the most dynamic moments.

A pool breaks that link — memory is allocated in advance and in a fixed amount, while at runtime an object merely receives an "active" flag and initial parameters. The heap stops growing, and the collector gets no reason for an unscheduled pass.

Reuse demands discipline that regular instantiation doesn't have. An object returns to the pool not "as is" but reset to its initial state: timers cleared, references to external data nulled, animations and coroutines stopped.

If this isn't done, the next "bullet" will inherit someone else's state — hence the classic bugs with stuck effects and objects that don't disappear. That's why returning to the pool is as explicit an operation as retrieving from it, not a side effect of deletion.

In terms of performance, the comparison doesn't favor constant instantiation even on weak devices. A pool eliminates not only the allocation but also the accompanying work: finding free space in the heap, updating references, the subsequent cleanup.

Frame time becomes predictable — instead of rare but deep dips, we get a smooth graph. For the player, this is the difference between "sometimes stutters" and "runs smoothly throughout the level."

There is a cost, too: a pool keeps memory occupied at all times, even when the objects aren't needed. That's why the size is chosen based on the scene's peak load rather than "with a margin just in case" — otherwise the performance gain is traded for excess consumption.

A competent implementation is a balance between the reserved amount and the actual peak of activity.

Common technical concerns about object pooling

Pooling integrates into an already running project without rewriting game logic: what changes is not the behavior of objects, but the way they are obtained and returned.

If the game is already built and released, we start with one class — projectiles, enemies, or particles — where the frame rate most often drops, and only after measuring do we migrate the rest.

We verify compatibility with the current architecture on that same section: the pool does not conflict with existing scene systems, and if necessary wraps them.

The complexity of adoption depends on how scattered object creation is across the code. When Instantiate is called in dozens of places, we first consolidate them into a single point via a factory — that is the main work, and the pool itself is added on top.

There is one risk at this stage: forgetting to return an object to the pool, causing it to "stick" as active. We close this with a state reset on retrieval and a check in tests, rather than relying on the developer's discipline.

Can a pool be introduced if the project is already deployed and in release?

Yes, and this is a typical scenario. Pooling does not require stopping development or rebuilding the architecture — a section is migrated to the pool incrementally, one object type at a time, with verification at each step.

If something goes wrong, the rollback is local: we restore the previous creation call for a specific class, and the rest of the game is unaffected.

Will the pool break the existing logic of objects?

The pool changes only the moment of creation and reuse, not the behavior. On retrieval, each object receives a reset state — position, velocity, timers, references — so the logic sees it as new.

A risk arises if an object stores data between lives; we move such fields into an explicit reset and cover them with a check.

How difficult is this if object creation is scattered across the code?

This is the most labor-intensive part, and it is worth doing gradually. First we find all creation points of the required type, consolidate them into a single retrieval interface, and only then connect the pool.

This way the integration does not turn into a large rewriting commit, but proceeds in sections with a clear result at each one.

Let's discuss your task of optimizing the object pool

Tell us what you already have: the engine and version, the genre, and what stage the project is at — prototype, beta, or a release with updates.

If the pool is already implemented but behaves unstably, it's useful to know how it's structured now: where objects are created, who returns them to the pool, and whether there's any separation by type. That's enough to understand the class of the task and propose an optimization solution.

Object pooling in Unity almost always comes down not to the pool itself, but to how objects leave and return to it. We look at the object lifecycle, at peak load moments, and at what happens when the scene reloads.

Then comes the estimate and the plan: which changes will have an immediate effect and which require reworking the architecture.

  • Analysis of the current pool implementation and load growth points
  • Estimate of the scope of work and risks for each scenario
  • A phased optimization plan without stopping development
  • Recommendations on pool structure for your genre and game profile
  • Options for integration with existing code and scene systems
  • Criteria by which we will measure the result after implementation

Describe the task in free form — what's bothering you, which metrics are dropping, what you've already tried. We'll come back with an estimate and a plan showing which steps produce which effect and in what order it makes sense to do them.