Handler Lifetimes in MicroMediator
Why Your Choice Matters
Most developers reach for a mediator library, register their handlers, and move on. Lifetime management is an afterthought. That decision quietly shapes the performance and correctness of every request your application processes.
MicroMediator makes lifetime explicit by design. There is no global default. You choose Singleton, Scoped, or Transient per handler at registration. That forces a decision that most libraries let you skip.
This post explains what each lifetime means, how to choose between them, and what the performance difference looks like in practice.
The three lifetimes
Singleton
A Singleton handler is created once and shared across every request for the lifetime of the application.
builder.Services
.AddMediator()
.AddSingletonHandler<GetProductByIdQueryHandler>();
MicroMediator caches the handler instance and the dispatch wrapper after the first request. Subsequent calls resolve with no DI overhead. Benchmarks show 24.64ns per request and 96 bytes allocated on .NET 10 (Intel Core Ultra 7 165H).
Use Singleton when your handler has no dependencies, or only depends on other Singleton services. Stateless handlers are the most common case: database reads where the repository is itself Singleton, pure transformations, in-memory lookups.
The constraint is thread safety. A Singleton handler will be called concurrently. If it holds mutable state, you have a race condition.
Scoped
A Scoped handler is created once per request scope. In ASP.NET Core, that maps to once per HTTP request.
builder.Services
.AddMediator()
.AddScopedHandler<CreateOrderCommandHandler>();
Use Scoped when your handler depends on Scoped services. The most common cases are DbContext, HttpContext, and unit-of-work patterns. These services are designed to live for exactly one request. Injecting them into a Singleton handler causes a captive dependency problem: the long-lived handler holds a reference to a short-lived service, which has likely already been disposed.
Performance is lower than Singleton because DI resolution happens per request. In practice, that overhead is negligible compared to a database call.
Transient
A Transient handler is created fresh for every request.
builder.Services
.AddMediator()
.AddTransientHandler<ProcessPaymentCommandHandler>();
Use Transient when your handler needs to hold state that is unique to a single operation. This is the least common case. Most handlers are either stateless (Singleton) or tied to a request scope (Scoped).
Performance is similar to Scoped since both require DI resolution per request.
Choosing the right lifetime
The decision follows a simple rule: match the lifetime of your handler to the shortest-lived dependency it takes.
| Handler depends on | Use |
|---|---|
| Nothing, or only Singleton services | Singleton |
| DbContext, HttpContext, or other Scoped services | Scoped |
| Per-operation mutable state | Transient |
When in doubt, start with Scoped. It is safe for the majority of real-world handlers and avoids the captive dependency trap. Promote to Singleton once you are confident the handler and all its dependencies are stateless.
What the benchmarks show
All benchmarks run on .NET 10.0, Intel Core Ultra 7 165H, BenchmarkDotNet v0.15.8.
Basic dispatch
The baseline comparison uses a Singleton handler with identical logic in both libraries — a simple value multiplication with no pipeline overhead.
| Method | Mean | Allocated |
|---|---|---|
| MicroMediator (Singleton) | 24.64 ns | 96 B |
| MediatR | 55.86 ns | 296 B |
MicroMediator dispatches in roughly half the time and allocates 3x less. The gap comes from the dispatch mechanism: MicroMediator caches a typed wrapper per request type after the first call. MediatR resolves through reflection on each request.
With validation pipeline
Adding a FluentValidation pipeline behaviour to both libraries:
| Method | Mean | Allocated |
|---|---|---|
| MicroMediator | 378.0 ns | 1.65 KB |
| MediatR | 824.1 ns | 4.02 KB |
The ratio holds. MicroMediator runs the same validation logic in less than half the time with less than half the allocation.
Throughput at scale
At 10,000 sequential requests, raw execution time converges because the handler logic dominates:
| Method | Mean | Allocated |
|---|---|---|
| MicroMediator Sequential | 445 μs | 234 KB |
| MediatR Sequential | 451 μs | 2,187 KB |
Execution time is within 1% of each other. Memory allocation is not: MediatR allocates 9x more across the same workload. At sustained load in a memory-constrained environment, that difference compounds through GC pressure.
Cold start
Cold start matters for serverless functions and containerised workloads where instances spin up under load.
| Method | Mean | Allocated |
|---|---|---|
| MicroMediator | 397 μs | 9.5 KB |
| MediatR | 868 μs | 602 KB |
MicroMediator initialises 2.2x faster and allocates 63x less on first request. MediatR's assembly scanning drives the allocation cost. MicroMediator's explicit registration scans nothing.
Explicit registration as a forcing function
Most mediator libraries scan assemblies and register handlers automatically. Convenient, but it hides lifetime decisions. You get transient by default without thinking about it.
MicroMediator requires explicit registration. That feels like friction at first. In practice it means lifetime is always a conscious choice, not an accident.
For small projects the difference is minor. For enterprise codebases with dozens of handlers spanning multiple teams, explicit lifetimes reduce the class of bugs that are genuinely hard to diagnose. The cold start numbers above show the other payoff: no assembly scanning means faster initialisation and a fraction of the startup allocation.
What comes next
The next post covers streaming large datasets with IAsyncEnumerable. Understanding Singleton handler performance matters there too, because streaming handler registration follows the same lifetime model.
The package is available on NuGet: TechnicalDogsbody.MicroMediator. Source and benchmarks are on GitHub.
Andy Blyth
Andy Blyth, an Optimizely MVP (OMVP) and Technical Architect at 26 DX with a keen interest in martial arts, occasionally ventures into blogging when memory serves.



