Memory pooling is one of the most powerful performance optimizations in ASP.NET Core, but misuse can negate its benefits and even make your application worse than if you didn’t pool at all.
Even with .NET 10’s automatic pool trimming, these anti-patterns still cause runaway memory usage, GC pressure, and false memory-leak reports in real systems. As an ASP.NET Core architect, I’ve seen these mistakes repeatedly in production, so here’s a practical guide to avoiding them.
1. Holding Pooled Buffers in Static Fields
The Anti-Pattern
public static class BufferCache
{
public static byte[] SharedBuffer =
ArrayPool<byte>.Shared.Rent(1024 * 1024);
}
Why It’s Dangerous
Buffer is never returned
Pool trimming cannot reclaim it
Memory becomes permanently pinned
Mimics a memory leak in production
Correct Approach
byte[] buffer = ArrayPool<byte>.Shared.Rent(1024 * 1024);
try
{
// Use buffer
}
finally
{
ArrayPool<byte>.Shared.Return(buffer);
}
Rule: If you didn’t rent it inside the method, you probably shouldn’t hold it elsewhere.
2. Treating Pooled Objects as Reusable State
The Anti-Pattern
var buffer = ArrayPool<byte>.Shared.Rent(4096);
_myService.CurrentBuffer = buffer; // Storing business data
Why It Breaks Things
Pooled memory is not owned
Contents are undefined after return
Can cause data corruption
Breaks concurrency safety
Correct Approach
Copy persistent data out of the buffer
Treat pooled memory as temporary scratch space only
3. Forgetting to Return Buffers on Exception Paths
The Anti-Pattern
var buffer = ArrayPool<byte>.Shared.Rent(8192);
Process(buffer); // Exception may occur
ArrayPool<byte>.Shared.Return(buffer);
Why This Is Costly
Leaks buffers under load
Pool grows unnecessarily
GC pressure increases
Long-running services degrade over time
Correct Pattern
var buffer = ArrayPool<byte>.Shared.Rent(8192);
try
{
Process(buffer);
}
finally
{
ArrayPool<byte>.Shared.Return(buffer);
}
Rule: If it’s rented, it belongs in a try/finally.
4. Over-Pooling Small, Short-Lived Objects
The Anti-Pattern
byte[] buffer = ArrayPool<byte>.Shared.Rent(32);
Why This Is a Mistake
Small allocations are cheap
Pooling adds CPU and complexity overhead
Makes code harder to maintain
Better Approach
Span<byte> buffer = stackalloc byte[32];
// or
byte[] buffer = new byte[32];
Rule: Pool large or frequently reused buffers—not tiny ones.
Join the conversation! Your thoughts help the community grow.