Blog
Mert Özen's blog posts on software development, backend, frontend, and DevOps.
Handling Text Correctly: Normalization, Diagnosing Mojibake and Base64
Two strings that look the same may not be equal, because "ş" can be written as one code point (NFC) or as s plus a combining mark (NFD); the fix is to bring text to NFC at the edge of the system. Search keys with NFKC, culture-dependent search, diagnosing and repairing mojibake like "ÅŸ" with a hex dump, the CSV/Excel BOM, HTTP charset, the N prefix and UTF-8 collations in SQL Server, Base64/Base64Url and why a JWT isn't encrypted.
Unicode and UTF-8/UTF-16: Code Points, Surrogate Pairs, Rune and Why string.Length Breaks on Emoji
string.Length breaks on emoji because .NET and JavaScript count strings in UTF-16 code units; the family emoji is one grapheme but 5 code points, 8 code units and 18 UTF-8 bytes. From ASCII to Unicode, code points, planes and scalar values, encoding UTF-8 and surrogate pairs by hand, Rune, u8 literals and the BOM, and counting a 280-character review limit correctly with StringInfo and Intl.Segmenter.
How Floating Point Works: IEEE 754, 0.1 + 0.2, NaN and double vs decimal
0.1 + 0.2 isn't 0.3 because a double stores numbers in base 2 with a sign, an exponent and a 52-bit mantissa, and 0.1 can't be written exactly in that base. The binary64 bit layout, gaps that widen as numbers grow, ±0, NaN and subnormals, comparing with relative tolerance, Half/float/double/decimal, the saturating conversions of .NET 9 and .NET 11, banker's rounding and culture-dependent parsing.
Integer Representation: Two's Complement, Overflow, Endianness and the Lost long ID in JSON
Computers store negative integers in two's complement: invert the bits and add 1, so a single adder both adds and subtracts and there is only one zero. The int.MinValue asymmetry and the Math.Abs exception, wrap-around, widths from byte to Int128, endianness and BinaryPrimitives, a long ID above 2^53 silently changing in JSON, Y2038 and an exhausted int identity.
Bitwise Operations: AND, OR, XOR, Shifts and Bit Masks for Flags
Bitwise operators process the bits at the same position in two numbers independently; AND filters, OR adds, XOR flips, shifts move. Setting, clearing, toggling and testing Bookstore permissions in one integer, BitOperations, the C# 11 >>> operator, masked shift counts, and how TypeScript squeezes every bitwise operation into 32 bits and silently breaks large IDs.
Binary and Hexadecimal: Positional Notation, Bits, Bytes and Conversions
Hex is shorthand for binary because every two characters map to exactly one byte: four bits make one hex digit. Positional notation, converting by hand, C# and TypeScript literals and formats, reading color codes, GUIDs, hashes and magic numbers, and why a 500 GB disk shows 465 GB in Windows.
SIMD and Vectorization: Processing Many Values at Once with Vector128/256/512 and TensorPrimitives
SIMD means processing 4, 8 or 16 values held in a wide register at once with a single instruction; in .NET you use it through the BCL, TensorPrimitives, Vector<T> and Vector128/256/512. Summing a million ints, a single-accumulator vector loop is 2x faster and a four-accumulator one 6.5x; plus vpaddd ymm/zmm in x64 assembly, IsHardwareAccelerated as a JIT constant, and double sums that change with the hardware.
Multi-Core, Hyper-Threading and False Sharing: Why More Cores Don't Scale Linearly
More cores don't scale performance linearly because cores share cache lines, memory bandwidth and execution units. In a four-worker Bookstore counter, a shared Interlocked counter runs 15x slower, adjacent per-worker slots can't beat a single thread because of false sharing, padding and local variables fix it, Environment.ProcessorCount follows container CPU limits, and Hyper-Threading and P/E cores change the picture.
How CPU Caches Work: Cache Lines, Associativity and Cache-Friendly C#
The CPU reads cache lines, not bytes (64 bytes on x64), and looks up an address by splitting it into tag, index and offset and searching a single set; how fast your code runs depends on whether it fits that structure. Walking a 4096 × 4096 table by columns 6.5x slower, a loop 4.6x faster once 16 columns remove the conflict misses, a 5x win from Struct of Arrays, and a struct shrinking from 40 to 24 bytes by field order.
The Memory Hierarchy: Latency Numbers from Registers to SSD and Locality
Memory is not one flat thing: from registers and L1 down to DRAM, SSD and the network, every layer is bigger and slower, and a DRAM read takes about 90 times as long as an L1 read. Summing a 256 MB array in shuffled order is 13.4x slower, you can measure latency on your own machine with pointer chasing, stack and heap are the same RAM, and a struct array scans 6x faster than a List<Book>.
CPU Pipelines and Branch Prediction: Why Is a Sorted Array Processed Faster?
A sorted array is processed faster because the ifs inside the loop become predictable and the CPU doesn't have to flush its pipeline. A 9.4x gap on one million orders, why the JIT won't emit cmov inside a loop, branchless summing with a bit mask, dependency chains and ILP, and why Spectre exists.
How a Line of C# Runs on the CPU: From Source to IL, Assembly and Instructions
A line of C# reaches the CPU through two translations: Roslyn turns it into IL, and at run time the JIT turns that into machine code for the machine's ISA, which the CPU runs in a fetch-decode-execute cycle. We read the 30-byte x64 Tier-1 code of TotalQuantity line by line, compare it with its ARM64 version, the 789-byte decimal version and an unoptimized Tier-0 measurement, and explain the x86-64-v2 baseline in .NET 11.