Sep 02

TimeProvider: test a 30-second retry policy in 13 milliseconds

A retry policy that backs off 5s, 10s and 15s can be tested against its real schedule in about 13 milliseconds, because .NET 8's TimeProvider is accepted by the framework's own waiting primitives. The part that costs an afternoon is that a fake clock only fires the timers that already exist, so one big jump past the whole schedule leaves the run pending.

dotnet · testing · 6 min
→
Sep 01

Use satisfies to check a config object without widening its types

Annotating a config object checks it and then replaces what you wrote with the type you checked against. satisfies runs the same check and leaves your types alone — so ink is still a string, the colour is still a tuple, and a typo in a key is still an error.

typescript · patterns · 5 min
→
Aug 29

What track $index actually costs in Angular's @for

track $index is usually described as the slow option. Measured, it is often the cheap one — a prepend costs it the same single DOM insertion, and a shuffle costs it zero structural DOM work. What it costs is correctness: every row's half-typed input and component state slides onto the row next door.

angular · patterns · 5 min
→
Aug 27

Stream your export: 6.3 seconds to first byte becomes 29 milliseconds

A buffered export reads every row before it answers. Over the same source, streaming the rows moved time-to-first-byte from 6.3 seconds to 29 milliseconds and peak heap from 39 MB to 17 MB — without finishing much sooner or sending a byte less. The costs are real too, and one of them arrives as a 200.

dotnet · performance · 6 min
→
Aug 25

AsSplitQuery: why a second Include returns 6,000 rows instead of 510

One collection Include is a join. The second one at the same level is a cross product: in the demo, 6,000 rows and 1.6 MB where 510 rows and 78 KB would do. AsSplitQuery fixes it, and in EF 10 the Skip/Take paging behind it also stopped being a correctness risk.

dotnet · performance · 6 min
→
Aug 23

Make a dead BackgroundService show up in your health check

A BackgroundService whose ExecuteAsync returns is finished for the life of the process, and nothing logs it. The stock health endpoint keeps answering 200 Healthy. Two lines of DI and one exception filter fix both halves.

dotnet · architecture · 7 min
→
Aug 22

GitGlass, a VS Code-style repo viewer in under 10 KB of vanilla JS

I wanted readers to browse a demo repo without leaving the article. Nothing embeddable was small enough, so I built one: tree, tabs, highlighting, themes, snippets and guided tours in under 10 KB of JS, with no dependencies and no backend.

javascript · tooling · 6 min
→
Aug 18

CancellationToken: stop doing work for callers who already left

ASP.NET Core can tell when a caller disconnects. Most handlers never ask. What CancellationToken actually does, where to pass it, and where to be careful.

dotnet · performance · 6 min
→
Aug 11

Replace isLoading, error and data with one discriminated union

isLoading, error, data — three flags, eight combinations, half of them lies. Discriminated unions make the impossible states impossible, and the compiler enforces it.

angular · patterns · 5 min
→
Aug 04

HybridCache stops the cache stampede that IMemoryCache allows

GetOrCreateAsync on IMemoryCache lets every concurrent request run the factory on a cold key. HybridCache's single-flight fixes the stampede you only meet under load.

dotnet · performance · 5 min
→