As .NET developers, we often focus on writing clean, maintainable code — but in high-throughput systems (APIs, microservices, real-time apps), performance is not optional — it’s essential.

Poor performance can lead to:

  • High memory pressure → frequent GC pauses
  • Slow response times → bad UX or SLA violations
  • Scalability bottlenecks → expensive infrastructure

In this guide, I’ll walk you through practical, battle-tested C# performance optimizations that I’ve used in production — from memory management to async patterns, data structures, and modern language features.

1. Reduce Memory Allocations with Span<T> and ArrayPool

❌ Problem: Excessive heap allocations slow down your app.

// Bad: Creates new string every time
string value = input.Substring(5, 10);

✅ Solution: Use Span<T> for zero-allocation slicing.

ReadOnlySpan<char> span = input.AsSpan().Slice(5, 10);
// No allocation! Fast and safe.

💡Use Case: Parsing large strings, CSV/JSON processing, network buffers.

2. Optimize Collections: Dictionary > List for Lookups

❌ Problem: O(n) lookup in large lists.

var user = users.FirstOrDefault(u => u.Id == targetId); // O(n)

✅ Solution: Use Dictionary<TKey, TValue> for O(1) lookups.

var dict = users.ToDictionary(u => u.Id); // O(1) lookup
var user = dict[targetId];

⚠️Warning: Don’t use mutable properties as keys — they break has consistency!

So, only do this in the following cases:

You have a unique, stable key (like Id, Guid)

The collection is queried frequently

Data size is large ( > 1000 items)

3. Master Async/Await: Avoid Blocking & Use ValueTask

❌ Problem: .Result or .Wait() blocks threads → deadlocks + poor scalability.

var result = GetDataAsync().Result; // 🚫 Dangerous!

✅ Solution: Always await.

var result = await GetDataAsync(); // ✅ Safe & efficient

Use ValueTask<T> in Hot Paths

When results are often already available (e.g., cache hit), avoid Task<T> overhead.

public async ValueTask<string> GetDataAsync()
{
if (cache.TryGet(key, out var data))
return data; // No Task allocation!

return await LoadFromDatabaseAsync();
}

📊 Impact: In high-RPS APIs, reduced allocations by 40%

4. Choose the Right Data Structure

Avoid foreach + modify list → use for loop backwards or ToList() copy.

5. Avoid Common Pitfalls

❌ String Concatenation in Loops

string html = "";
for (int i = 0; i < 1000; i++)
html += "<div>" + i + "</div>"; // 🚫 1000 allocations!

✅ Use StringBuilder

var sb = new StringBuilder();
for (int i = 0; i < 1000; i++)
sb.Append("<div>").Append(i).Append("</div>");
string html = sb.ToString();

📉 Memory savings: Up to 80% reduction in allocations.

❌ Event Handler Leaks

button.Click += MyHandler; // Forgot to unsubscribe? Memory leak!

✅ Always Unsubscribe

button.Click -= MyHandler; // Or use weak event pattern

Use dotMemory or Visual Studio Profiler to detect leaks.

6. Benchmark & Profile Like a Pro

Don’t guess — measure!

✅ Tools You Should Know:

Example: Use Stopwatch + BenchmarkDotNet to prove your optimization works.

[Benchmark]
public void OldMethod() { /* ... */ }

[Benchmark]
public void NewMethod() { /* ... */ }

7. Modern C# Features That Boost Performance

a) record + init-only Properties

public record User(int Id, string Name)
{
public string Email { get; init; } // Set only during construction
}

Immutable, concise, and auto-generated equality methods.

b) ref struct and stackalloc

Span<int> numbers = stackalloc int[10]; // Allocated on stack!

Only usable in local scope — perfect for temporary buffers.

c) System.Text.Json over Newtonsoft.Json

var json = JsonSerializer.Serialize(data); // Faster, less allocation

Benchmarks show ~2x faster serialization in many cases.

Real-World Impact Summary

📚 Further Reading

Reduce memory allocations using new C# features
ASP.NET Core Best Practices
BenchmarkDotNet GitHub
C# 10+ Language Features

🎯 Conclusion

Performance tuning in C# isn’t about magic tricks — it’s about understanding the runtime, choosing the right tools, and measuring impact.

Whether you’re building a high-scale API, a real-time game server, or a background worker — these techniques will help you write faster, leaner, and more scalable C# code.

Start small. Pick one optimization. Measure the difference. Repeat.

In the end, if you enjoyed this article, please

  1. Clap 10 times (seriously, it motivates me!) 👏
  2. Subscribe for more tutorials.
  3. Follow me to never miss an update.

Got Questions? Ask below! I’ll reply to every comment.