I reach for a memory arena when I know two things up front: the data is temporary, and the temporary data all dies together.

That sounds narrow because it is narrow. An arena is not a magic optimization bucket. It is a very specific answer to a very specific lifetime problem: allocate a bunch of scratch data quickly, use it for one phase of work, then throw the whole thing away in one shot.

That is a good fit for game code more often than people expect. Frame-local simulation data, temporary pathfinding buffers, animation build steps, UI layout scratch, packet decoding, command buffering, visibility lists, import pipelines — all of those can be arena-shaped if the lifetime is tidy.

The moment the lifetime stops being tidy, the arena stops being elegant.

Arena lifetime diagram

What an arena actually is

The core idea is simple.

An arena starts with one big block of memory. Allocation means moving a cursor forward. Deallocation is usually not per-object cleanup. It is a reset.

That gives me a few useful properties:

  • allocation is cheap
  • lifetime is obvious
  • memory tends to stay contiguous
  • I do not pay bookkeeping for every tiny object

Most arena implementations are just a bump pointer plus a reset point.

That is the whole trick.

The reason it works so well is that many game workloads are phase based. I do some work, I need temporary storage while I do it, and once the phase ends I do not care about the intermediate values anymore. If I can say that out loud, I usually have an arena candidate.

If I cannot say that out loud, I usually do not.

Why game code likes arenas

Games create a lot of temporary structure.

A pathfinding query needs an open list, a closed list, a cost table, and maybe some extra scratch to reconstruct a path. A UI layout pass may need temporary rectangles, text measurements, and a few layout stacks. An animation build step may need a batch of baked curves or pose data. A visibility pass may need a short-lived list of candidates before the real filter runs.

Arenas help because they match the shape of the work.

I do not care about those objects one by one. I care that the whole batch belongs to one step in the pipeline. When the step finishes, the arena can be rewound and reused.

That is the real value. Not raw speed, although speed often follows. The bigger win is that the code starts telling the truth about its lifetime.

A thousand individual frees are usually a smell in game code. They often mean I have let a temporary phase leak into the rest of the system.

The lifetime question is the real design question

Every time I consider an arena, I ask one boring question:

Does this data die at the same time as the thing that created it?

If the answer is yes, an arena is probably a good fit.

If the answer is “mostly” or “sometimes” or “I guess so,” I slow down. Arena allocation gets messy when objects have mixed lifetimes. The moment some entries must survive and others must disappear, the clean reset story breaks down.

That is where a lot of people get burned.

They choose an arena because they want to avoid fragmentation, but then they start keeping long-lived references into arena memory, or they tuck one persistent object inside a sea of scratch allocations, and suddenly the nice simple rule has turned into a bug factory.

Arenas want phase boundaries. They do not like exceptions.

Arena versus pool versus garbage collection

I do not treat these as competing religions. They solve different problems.

Garbage collection is fine when the runtime owns the lifecycle and the allocation rate is reasonable. The problem in game code is not that GC exists. The problem is that a hot path can generate enough pressure to make collection visible at the wrong time.

Object pools are useful when I want to reuse expensive objects individually. A pool is a good fit when I keep renting the same shape over and over, but the lifetime is not perfectly synchronized across the whole batch.

Arenas are for phase-local scratch data. They are simpler than a pool when the lifetime is batch-shaped.

That last distinction matters.

If I need a reusable bullet object, a pool may be the right answer. If I need temporary collision candidates for a single simulation step, an arena is often better. If I need ordinary game state with rich behavior and long-lived ownership, the managed heap or normal engine objects may still be the right tool.

I want the smallest tool that tells the truth about the lifetime.

Unity already gives me arena-like tools

In Unity, I do not need to invent the concept from scratch to use the idea.

Unity’s allocator docs split unmanaged memory into three useful lifetime buckets:

  • Allocator.Temp for very short-lived work
  • Allocator.TempJob for short-lived work that can cross into jobs
  • Allocator.Persistent for long-lived allocations

That model already smells like arena thinking.

Allocator.Temp is the purest example. It is the “use this now and throw it away immediately” bucket. Unity documents it as the fastest allocator, which is exactly what I would expect from a scratch-space design.

Allocator.TempJob is still temporary, but the lifetime is long enough to survive a job boundary.

Allocator.Persistent is the opposite end of the spectrum. It is not for scratch work. It is for data that sticks around.

So when I work in Unity, I usually ask whether I can make the engine’s own lifetime buckets do the job before I reach for something custom. That is the lazy answer, and the lazy answer is often the correct one.

Array pools are useful, but they are not arenas

This is where people mix terms and then wonder why the code feels odd.

.NET gives me ArrayPool<T>, and Microsoft’s docs are clear that renting and returning buffers is useful when arrays are created and destroyed frequently because it reduces garbage collector pressure.

That is helpful, but it is not the same thing as an arena.

A pool recycles individual buffers. An arena usually resets the whole batch at once.

That difference changes how I think about ownership:

  • pool: I rent one thing, use it, return it
  • arena: I allocate many things, use them together, reset the whole region

Pools are good when I need reuse but do not have a single clean phase boundary. Arenas are good when I do have that boundary.

If I use the wrong one, I usually end up fighting the lifecycle model instead of benefiting from it.

A small arena shape in C#

For a lot of game tools, I do not need a giant allocator framework. I need one bounded scratch buffer with an explicit reset.

This is the shape I mean:

using System;
using System.Buffers;

public sealed class FrameScratch : IDisposable
{
    private readonly byte[] _buffer;
    private int _offset;

    public FrameScratch(int size)
    {
        _buffer = ArrayPool<byte>.Shared.Rent(size);
        _offset = 0;
    }

    public Span<byte> Allocate(int bytes)
    {
        if (bytes < 0)
            throw new ArgumentOutOfRangeException(nameof(bytes));

        if (_offset + bytes > _buffer.Length)
            throw new InvalidOperationException("FrameScratch exhausted");

        var slice = _buffer.AsSpan(_offset, bytes);
        _offset += bytes;
        return slice;
    }

    public void Reset()
    {
        _offset = 0;
    }

    public void Dispose()
    {
        ArrayPool<byte>.Shared.Return(_buffer);
    }
}

That is not a universal allocator. It is intentionally limited.

The ceiling is obvious: one buffer, one cursor, one reset point. If I need typed storage, alignment, nested lifetimes, or concurrent writers, I should build or use something stronger. But for phase-local scratch data, this is often enough.

The important part is that the code advertises its lifetime.

Where I use it

I like arenas for work that already has a natural batch.

A few examples:

  • pathfinding queries that need temporary search data
  • procedural generation passes that build intermediate structures
  • text layout and UI measurement passes
  • importer and baker tools that chew through a lot of temporary data
  • gameplay simulation steps that gather candidates, then discard them all together
  • network decode or command parsing where the temp state is only needed while processing one packet

That list is not exhaustive. It is just the pattern I look for: a burst of temporary data, one owner, one phase, one reset.

If the code starts storing references to the scratch data somewhere else, I treat that as a warning sign. An arena is not a way to avoid ownership. It is a way to make ownership simpler.

Where I do not use it

Arenas are a bad fit when the data has mixed lifetimes or needs individual disposal.

I avoid them when:

  • objects survive across many frames
  • the data is part of persistent game state
  • different entries expire at different times
  • I need to delete arbitrary items in the middle of the set
  • I want the allocation boundary to be invisible
  • the memory must outlive the current phase but I keep pretending it will not

That last one is the classic mistake.

An arena is not a shortcut around thinking about ownership. It is a way to make the ownership rule brutally clear.

If the rule is not clear, the arena is the wrong tool.

The two mistakes I see most often

1. Using an arena for everything

This is the classic overcorrection.

Someone discovers arenas and starts feeding them every allocation in the project. That usually creates a worse mess than the garbage collector ever did. Long-lived state, temporary scratch, cached assets, and ephemeral buffers all start sharing one mechanism that was only designed for one kind of lifetime.

That is not elegance. That is denial with better performance numbers.

2. Forgetting the reset boundary

The other mistake is subtler.

The arena works beautifully in a local test, and then someone forgets to reset it at the end of the frame or phase. Memory usage climbs. The code still “works,” which makes the bug annoying to catch. It only looks like a leak because it is one in practice.

An arena without a clear reset point is just a buffer that gets less useful over time.

What I look for before I introduce one

Before I add an arena, I want three things to be true:

  1. the lifetime is short and obvious
  2. the data is temporary and batch-shaped
  3. the code has a natural place to reset

If those three are true, the arena usually pulls its weight.

If they are not true, I usually do something simpler.

That might mean a normal List<T>, a pool, Unity’s built-in allocator lifetime buckets, or just leaving the code alone because the allocation cost is not actually the bottleneck.

That last part is not cowardice. It is a useful habit.

Not every temporary allocation problem is big enough to deserve a custom allocator.

The practical rule I keep

My rule is simple:

  • if the data belongs to one phase, use a phase-local allocation strategy
  • if the data belongs to one object, let the object own it
  • if the data belongs to many unrelated lifetimes, do not force an arena on it

That keeps me from overengineering the allocator and underengineering the ownership model.

Arenas are excellent when they stay boring.

When they start becoming clever, they usually stop being worth the trouble.

Takeaway

I use memory arenas when I want fast scratch space with a hard reset boundary.

They are not a replacement for the GC, and they are not a fancy pool. They are a lifetime tool. If I can say exactly when the data dies, the arena is a strong candidate. If I cannot, I leave the memory model alone and pick something simpler.

The best allocator is the one that matches the shape of the work, not the one that sounds smartest.

References