← All posts
2026-06-02
CRAFT
6 min

Twenty years of C#, and what I had to unlearn for TypeScript

Interfaces are not contracts here. Structural typing is a gift and a trap. On giving up the class hierarchy I spent a career building.

The first TypeScript I wrote was C# with different punctuation. It compiled, it ran, and it was wrong in a way that took a year to see.

An interface is not a contract

In C# a type implements an interface because it says so. The declaration is the fact. In TypeScript a type satisfies an interface because its shape happens to match, and nothing anywhere records the intent. That is enormously freeing — you can describe a shape you do not own, and adapt a third-party object without a wrapper class. It is also how you end up with two unrelated types that are silently interchangeable because they both have an id and a name.

The fix is not to fight it. It is to stop treating interfaces as identity and start treating them as descriptions. Where identity actually matters, brand the type and be explicit about it.

The hierarchy you do not need

I spent a long time building inheritance trees to share behavior. Most of them existed to avoid duplicating twenty lines. In TypeScript the same job is done by a function that takes the thing and returns the other thing, and it is easier to test, easier to name, and impossible to break by adding a member three levels up.

What carried over

Nullability discipline. strictNullChecks is the closest thing TypeScript has to the nullable-reference-types work in modern C#, and twenty years of being burned by null made it free to adopt. Turn it on before the first line, never after the ten-thousandth.

Written by Martin Dahl. If you want to argue about any of it, say hello.