Asynchronous programming is a core part of modern .NET applications. But when it comes to performance-sensitive code, developers often face a difficult question:
Should I return Task or ValueTask?
At first glance, the difference appears trivial. In reality, the wrong choice can lead to unnecessary allocations, reduced throughput, and subtle bugs. This article provides a practical, real-world explanation, with clear rules to apply in your own applications.
1. What Problem Does ValueTask Solve?
Every time a method returns a Task, .NET allocates an object on the heap.
For most applications, this cost is negligible.
But in high-performance scenarios—where the same asynchronous method may be invoked millions of times—these allocations can accumulate and become expensive.
ValueTask<T> solves this by being a value type (struct), allowing .NET to avoid allocating a heap object when the result is already available synchronously.
2. Key Differences Between Task and ValueTask
| Feature | Task | ValueTask |
|---|---|---|
| Type | Reference type | Value type |
| Heap allocation | Always | Avoided if completed synchronously |
| Awaitable multiple times | Yes | No |
| Safe to store and reuse | Yes | No |
| Supports pooling | Yes | No (but can wrap pooled Tasks) |
| Ideal scenarios | General async code, I/O operations | Hot paths, synchronous fast-paths |
| Complexity | Low | Higher |
3. Example: A Good Use Case for ValueTask
Below is the classic example of a method that frequently returns synchronously from cache:
public ValueTask<string> GetItemAsync(string key)
{
if (_cache.TryGetValue(key, out var value))
{
return new ValueTask<string>(value); // No allocation
}
return new ValueTask<string>(LoadFromDatabaseAsync(key));
}
If LoadFromDatabaseAsync is only rarely called, ValueTask reduces the number of heap allocations dramatically.
4. A Bad Use Case for ValueTask
Use ValueTask incorrectly, and you may introduce subtle (and costly) bugs:
ValueTask<int> result = GetCachedValueAsync();
var a = await result;
var b = await result; // Not allowed — ValueTask may only be awaited once
With Task, this is perfectly safe.
5. Performance Considerations
When ValueTask Saves Time
ValueTask shines when:
- your method returns synchronously at least 80–90% of the time
- the method is called very frequently (hundreds of thousands of calls or more)
- you are inside a hot path such as parsers, protocol decoders, low-level libraries or high-throughput APIs
Removing millions of allocations per second can yield measurable improvements.
When ValueTask Makes Things Worse
Using ValueTask incorrectly can introduce:
- additional copying (because it's a struct)
- more IL instructions
- added complexity for consumers
- the risk of double-await bugs
- unnecessary boxing in generic scenarios
For most applications, these costs outweigh any benefit.
6. Real-World Guidelines
Use Task by default
It is simpler, safer, and perfectly adequate for the vast majority of .NET applications.
Only use ValueTask when:
- you have measured that Task allocation is a bottleneck
- your method often completes synchronously
- your code is part of a high-performance subsystem
Avoid ValueTask
- in public APIs (unless you really need it)
- when your method is rarely awaited synchronously
- when consumers will store or re-await the result
- when simplicity is more valuable than micro-optimisation
7. A Practical Example with Both Approaches
Using Task (recommended for most scenarios)
public Task<User> GetUserAsync(Guid id)
{
return _database.LoadUserAsync(id);
}
Clear, safe, conventional.
Using ValueTask (when data is often cached)
public ValueTask<User> GetUserAsync(Guid id)
{
if (_cache.TryGetValue(id, out var user))
{
return new ValueTask<User>(user);
}
return new ValueTask<User>(_database.LoadUserAsync(id));
}
Avoids allocation in the synchronous path.
8. The Rule of 90%
A popular rule among .NET performance engineers:
Only use ValueTask if your method returns synchronously at least 90% of the time and you have benchmarks proving allocation is a bottleneck.
This rule alone prevents most incorrect uses of ValueTask.
9. Conclusion
ValueTask is a powerful tool for high-performance .NET applications, but it is often misunderstood and misused. For most developers and most APIs, Task remains the best choice:
- simpler
- safer
- easier to reason about
- suitable for almost all workloads
Use ValueTask only when performance measurements justify it, and when you understand its limitations.
You now have a clear, reliable framework for deciding between Task and ValueTask in your own applications.

Top comments (2)
Great article really appreciate how clearly you explained when to use Task vs ValueTask. One small thing you could add, it’s worth calling out why multiple awaits on a ValueTask can be dangerous. Unlike Task, a ValueTask may be backed by a pooled IValueTaskSource, so the underlying operation can be returned to the pool and reused. That’s why AsTask() exists when you need safe multiple awaits.
Thanks 🙂