Blog
Mert Özen's blog posts on software development, backend, frontend, and DevOps.
How Programming Languages Evolve: Paradigms from Lisp to C# and the Road to Runtime Async
Language features are often born as a trick the compiler imitates with existing constructs, and once they spread the runtime supports them directly: generics, lambdas and .NET 11's Runtime Async are examples. A short family tree from Fortran and Lisp to C#, paradigms used together in C#, static/dynamic and strong/weak typing, the nominal versus structural difference between C# and TypeScript, and a live stack that drops from 13 lines to 5 with Runtime Async, all with real output.
How Garbage Collection Works: Mark-Sweep-Compact, Generational GC and .NET Server GC
A tracing GC marks the objects reachable from the roots and treats the rest as garbage; .NET splits this work into generations and keeps gen0 GCs cheap with a write barrier and a card table. With dotnet-counters and BenchmarkDotNet we show the Bookstore filter language's interpreter producing 10 GB of allocations and 1,365 gen0 GCs in five seconds while the compiled delegate triggers none, and measure the LOH, Workstation and Server GC, DATAS, and a real leak versus uncollected memory.
AOT Compilation: ReadyToRun, Native AOT and the Startup vs Throughput Trade-off
Native AOT produces machine code at publish time and clearly reduces startup time and memory; the price is trimming, no dynamic code, platform-specific builds and no Dynamic PGO. We publish the same Bookstore API with JIT, ReadyToRun and Native AOT and measure startup, memory and throughput, and show the warnings our filter language hits, a trimming bug that returns 500 without any warning, and the cost of Expression.Compile() falling back to an interpreter.
IL and the JIT Compiler: Tiered Compilation, Dynamic PGO, Inlining and Escape Analysis
The JIT does not compile a method once but in tiers: first unoptimized Tier0, then an instrumented copy, finally a Tier1 optimized with the collected profile. With Bookstore code and DOTNET_JitDisasm output we show IL's stack-based form, OSR, Dynamic PGO turning an interface call into direct code behind a type check, bounds check elimination and .NET 10's escape analysis taking a 40-byte allocation down to zero, and compare it with V8's hidden classes and deoptimization.
Semantic Analysis and Code Generation: Type Checking, Symbol Tables and Compiling an AST to Expression Trees
Semantic analysis binds the names in a parsed tree to a symbol table and checks types, so a mistake like price = "abc" is caught before the filter touches any book. For the Bookstore filter language we write a binder with implicit conversion and constant folding, compile the verified tree to Expression<Func<Book,bool>>, run it in memory and use ToQueryString to show EF Core turning it into parameterized SQL; we also see why an input length limit is needed and what Roslyn does.
Parsers and ASTs: Recursive Descent, Operator Precedence and Pratt Parsing
A parser turns tokens into a single AST following precedence and associativity rules; Pratt parsing keeps those rules in a binding power table instead of a chain of methods. For the Bookstore filter language we derive a recursive descent parser from the grammar, write the same language with a Pratt parser and test that both build the same tree, report several errors at once with panic mode, and run the AST with a tree-walking interpreter.
Writing a Lexer: From Characters to Tokens with a Bookstore Filter Language in C#
A lexer is a state machine that splits characters into tokens and knows the line and column of each one. We write a single-pass C# lexer over ReadOnlySpan<char> for the Bookstore filter language: maximal munch, keyword matching that survives the Turkish culture, number parsing with InvariantCulture, error recovery that respects surrogate pairs, and clear error messages. Real output shows where a regex lexer breaks and why Roslyn keeps whitespace as trivia.
How Code Becomes Runnable: A Map of Compilers, Interpreters, JIT and AOT
There is no such thing as a compiled or an interpreted language; which stages run at build time and which at run time is the implementation's choice. A C# method called 1,500 times is compiled by the JIT only three times, TypeScript types are checked at build time and erased, and V8 first interprets code and then compiles it with Maglev and TurboFan. The post shows the compiler pipeline with real output and drafts the grammar of the Bookstore filter language we build in this series.
Concurrency Models and Verifying Concurrent Code: Shared Memory, Actors, CSP and Model Checking
A concurrency model decides who owns the state and how parts communicate: with shared memory everyone takes turns through a lock, with actors and CSP the state has a single owner and communication is by message. We build Bookstore's stock reservation with a lock, an actor and a channel pipeline, show a read-then-write protocol selling 4,000 books from 1,000 in stock while the actor itself is bug-free, and model check the same check-then-act bug in TLA+ and read TLC's counterexample.
Lock-Free and Wait-Free: CAS Loops, the ABA Problem and Linearizability
A lock-free algorithm guarantees that at least one thread makes progress even under contention, a wait-free one that every thread does. We write a counter and a Treiber stack with CAS loops, and measure on ARM64 that pooling nodes brings back the ABA problem and loses hundreds of thousands of 8 million ids, that a small checker finds non-linearizable histories in a broken stack, and that the CAS loop is 16 times slower than a lock on 8 threads.
The .NET Memory Model: Happens-Before, Acquire/Release, volatile and Safe Object Publication
In the .NET memory model ordinary accesses may be reordered; Volatile.Read is an acquire, Volatile.Write a release, and Interlocked and lock are the points that guarantee order. We show a non-volatile stop flag loop never stopping in a Release build because of the JIT, double-checked locking with a bool flag returning null 36 times in 20 million calls on ARM64, derive from the spec why checking the reference is correct, and compare with Java, C++ and Go.
Hardware Memory Models: Reordering, Store Buffers, Cache Coherence and Sequential Consistency
Modern CPUs do not give sequential consistency: the store buffer lets a read overtake an earlier write, and ARM64 reorders even more. On ARM64 we measure both threads reading 0 in 5.3 million of 10 million rounds of the classic store buffering test and a flag becoming visible before the data in the message passing test, explain why cache coherence does not prevent this, and show from Intel's manual which outcomes x64 forbids.