1. Overusing async / await Without Understanding the Cost
The Mistake
Using async/await everywhere—even when no real async work is happening.
public async Task<int> GetCountAsync()
{
return 10;
}
Why It’s a Problem
Adds unnecessary state machines
Slight performance overhead
Misleads future readers into thinking I/O is involved
Better Approach
Only use async when awaiting actual asynchronous work.
public Task<int> GetCountAsync()
{
return Task.FromResult(10);
}
Rule of thumb: Async all the way—but only when async is actually needed.
2. Ignoring IDisposable and Resource Lifetimes
The Mistake
Forgetting to properly dispose of unmanaged resources.
var stream = new FileStream("data.txt", FileMode.Open);
// use stream
Why It’s a Problem
Correct Usage
using var stream = new FileStream("data.txt", FileMode.Open);
// use stream
Or (when async):
await using var stream = new FileStream("data.txt", FileMode.Open);
Senior pitfall: “The GC will clean it up” — not fast enough, not safely.
3. Misusing LINQ in Performance-Critical Code
The Mistake
Writing expressive LINQ without considering allocations and execution cost.
var result = users
.Where(u => u.IsActive)
.Select(u => u.Email)
.ToList();
Why It’s a Problem
Multiple enumerations
Hidden allocations
Harder debugging
When It Matters
Hot paths
Large collections
High-throughput services
Alternative
var result = new List<string>();
foreach (var user in users)
{
if (user.IsActive)
result.Add(user.Email);
}
Balance readability with performance, not blindly one or the other.
4. Overengineering With Abstractions
The Mistake
Introducing interfaces and layers too early.
IUserService → IUserRepository → IUserDataProvider → IUserStorage
Why It’s a Problem
Increased cognitive load
Harder debugging
Slower development
Better Principle
Abstract when there is real variation or proven need.
Ask:
If not, keep it simple.
5. Forgetting That DateTime Is Tricky
The Mistake
Using DateTime.Now everywhere.
var expiresAt = DateTime.Now.AddDays(1);
Why It’s a Problem
Better Approach
Prefer DateTime.UtcNow or DateTimeOffset.
var expiresAt = DateTimeOffset.UtcNow.AddDays(1);
Time bugs are notorious and often discovered in production.
6. Catching Exception Too Broadly
The Mistake
try
{
DoSomething();
}
catch (Exception ex)
{
Log(ex);
}
Why It’s Dangerous
Better Approach
Catch specific exceptions and handle intentionally.
catch (IOException ex)
{
Log(ex);
throw;
}
Senior rule: If you catch it, you must own it.
7. Not Understanding Value vs Reference Semantics
The Mistake
Assuming structs behave like classes.
public struct Counter
{
public int Value;
}
Passing it around and expecting mutations to persist.
Why It’s a Problem
Silent bugs
Unexpected copies
Performance issues
Key Reminder
Use structs only when:
Small
Immutable
Performance-critical
8. Skipping Meaningful Logging
The Mistake
_logger.LogError("Something went wrong");
Why It’s Useless
No context
No data
No way to reproduce
Better Logging
_logger.LogError(ex, "Failed to process order {OrderId}", orderId);
Logs are for future you at 3 AM.