~/sumit/portfolio — when-to-break-the-type-system.md
writing.md/When to break the type system
---
title: "When to break the type system"
date: 2024-11-22
tags: [engineering]
reading_time: 5 min
slug: when-to-break-the-type-system
---

When to break the type system

5 min read engineering by sumit

There's a category of TypeScript user who treats as any like an unforgivable sin. They will spend a full afternoon wrestling a generic constraint to avoid one. That user is usually me, and I'm increasingly convinced I'm wrong about it.

What the type system is for

The type system exists to prevent a specific class of bugs: wrong shape at call sites, wrong assumption about what a value can be. That's a job it does well.

It is not a substitute for tests. It is not a guarantee of correctness. It does not stop runtime data from being garbage. If you've ever parsed a JSON response and declared it User, you've told the compiler a story that might be a lie.

The honest escape hatches

TypeScript gives you three reasonable ways out:

  • as X — "trust me, this is X"
  • X | unknown — "this is X or I don't know, caller checks"
  • @ts-expect-error — "this line is wrong, and I know it"

All three are fine. @ts-expect-error is underused. Unlike @ts-ignore, it complains when the underlying issue is fixed — so it self-cleans.

When to use them

I reach for escape hatches when:

  • An external library's types are wrong, and upstreaming a fix would block shipping
  • A migration is mid-flight and the correct type is three refactors away
  • Narrowing would require a helper function whose only purpose is to appease the compiler

I don't use them when:

  • I don't understand why the type error is happening (that's the compiler doing its job)
  • The fix is actually five minutes away

The real cost

The cost of as any isn't in that line — it's in the next person who has to modify the code around it. If I leave a note why, most of that cost disappears:

// upstream type is incorrect, PR: library/foo#123
const user = res.data as User

A 25-character comment buys you a lot of forgiveness from future-you. Use it.