Blog
Mert Özen's blog posts on software development, backend, frontend, and DevOps.
Debugging Is a Method: Observation, Hypothesis, Experiment and the 9 Rules
Debugging is done with a method, not with guesses: a loop of observation, testable hypothesis, prediction, experiment and conclusion. Starting from a 1 kuruş order total mismatch, we cover Agans' 9 rules, the cost of shotgun debugging and the debugging journal with real outputs.
Reproducing the Bug: Minimal Repro, Environment Differences and a Failing Test First
You can't fix a bug you can't reproduce: get the right information from the reporter, check culture, time zone, configuration and version differences, reduce the bug to a minimal repro and write a failing xUnit test before the fix. With real outputs: 12,45 parsed as 1245 in a container and an order total bug reduced to two lines.
Narrowing It Down: Binary Search, git bisect run and Delta Debugging
The fastest way to find a bug is to halve the range at every step: in code, across layers, over commits and in the input. git bisect run skips a broken commit with exit code 125 and finds the faulty refactoring among 31 commits in 7 tests; the ddmin algorithm shrinks a 5,000-line import file to 3 lines.
Reading Error Messages and Stack Traces: .NET Exceptions, Async Stacks and Angular NG Errors
A stack trace is read layer by layer, not top to bottom: the real cause usually sits in the inner exception starting with --->, and the root of 50 identical errors in a log is usually the first error in time order. With real output from .NET 10 we read InnerException chains, AggregateException, the stack lost to throw ex, ExceptionDispatchInfo, going from a ProblemDetails traceId to the log, source maps, and Angular 22's NG0100, NG0200 and NG0201 errors.
Advanced .NET Debugging: Conditional Breakpoints, Tracepoints, Data Breakpoints and Exception Settings
The shortest way to find who changes a value is a data breakpoint: in Visual Studio, for .NET Core 3+ projects, the debugger stops on the line that writes the property. With real output we cover conditional, hit count and dependent breakpoints, tracepoints, Exception Settings for swallowed exceptions, Set Next Statement, Hot Reload, DebuggerDisplay and Source Link, testing Copilot suggestions as hypotheses, and the Rider and Visual Studio Code equivalents.
Async and Multi-Threaded Debugging: Parallel Stacks, the Tasks Window, Deadlocks and Race Conditions
In an async hang, the waiting code isn't on any thread's stack; you need the Tasks view of Parallel Stacks or the async chain in a dump. We read a lock-order deadlock and a .Result sync-over-async deadlock through dotnet-stack output, measure thread pool starvation, show Dictionary, counter and check-then-act race conditions with real runs, and explain forcing a race with freeze/thaw.
Bug Hunting in an Angular App: Signals, Change Detection, RxJS and the HTTP Layer
In Angular you narrow a bug by splitting the UI, state, HTTP and API chain down the middle. With real Vitest output: a computed that never updates because an array was mutated in place, a signal read after await that is not tracked, a stale screen and a hidden NG0100 under Angular 22's default OnPush, a search stream killed by catchError, switchMap cancellations and interceptor order.
Finding the Cause in Logs: Correlation IDs, Trace IDs and Building a Timeline
To find the cause of an error in logs we first find the first signal, then follow a single request across services by its trace ID, and finally build a shared timeline with deploys and configuration changes. Plus W3C traceparent, the traceId in ProblemDetails, raising the log level at runtime, jq queries and masking personal data in logs.
Analyzing Crash and Memory Dumps: dotnet-dump, SOS Commands and Taking Dumps in Containers
We take crash dumps automatically with DOTNET_DbgEnableMiniDump and use pe -lines, clrthreads, dumpheap -stat, gcroot, syncblk and dumpasync in dotnet-dump analyze to find the crashing thread, a growing static cache and a lock deadlock. Plus PID 1 versus --init in containers, getting the tool into an SDK-less image, the OOM risk of Heap/Full dumps and personal data inside dumps.
Diagnosing Hangs and Slowness: dotnet-stack, dotnet-trace and Thread Pool Starvation
We diagnose an unresponsive service with dotnet-counters, dotnet-stack and a time-boxed dotnet-trace, in that order: an endpoint using .Result pushes p50 to 47 seconds under 200 concurrent requests, and await brings it down to 204 ms. Plus WaitHandleWait events, the prerequisites of collect-linux in .NET 10 and keeping traces safely bounded in production.
Root Cause Analysis: 5 Whys, Fishbone and Blameless Postmortems
A root cause is not a person but the conditions that let a bug reach production. In the order total case we separate symptom, proximate cause and root cause, find the factors with 5 Whys and a fishbone, measure the impact, make the class of bug impossible with a Money value object and BannedApiAnalyzers, and write the blameless postmortem.
Real-World Bug Hunts 1: Backend — Culture, Time Zones, Sockets and Mutable Keys
In backend bugs the first hypothesis usually blames another system; a cheap observation refutes it. Six real cases: a role check broken under tr-TR, a report shifted 3 hours by UTC, TIME_WAIT sockets piling up from new HttpClient(), a key mutated inside a Dictionary, an async void crashing the process (and lingering half-dead as PID 1 in a container), and N+1 in EF Core; each with a fix, a regression test and an analyzer.