C# inherits a lot of Java's bug landscape — null reference exceptions, concurrency over shared state — and adds its own through async/await, LINQ, and the depth of the .NET standard library.
The recurring shapes: forgotten `await` on a `Task` (the same bug as JavaScript's missing-await, with the same symptoms), deadlocks from `.Result` or `.Wait()` on an async call from a UI thread, LINQ queries that look pure but mutate captured variables, and `IDisposable` types not wrapped in `using`.
Nullable reference types (`#nullable enable`) have closed a major chunk of the NRE surface for new code. The migration is incremental — the bugs hide in the not-yet-migrated parts of older codebases.
Common pitfalls
`.Result` deadlocks
`task.Result` on a UI/sync context blocks waiting for a continuation that needs the same context. Always await async code instead.
Forgotten await on Task
An async method called without `await` runs but the caller can't see exceptions or completion. The compiler warns; teams disable the warning at their peril.
Missing using
Streams, database connections, HTTP clients — anything `IDisposable` needs `using` or it leaks until the GC eventually finalizes.