Part 12 – Type Proofs
This is a series of posts I’m writing about using types as another tool in software development, Continuous Delivery, & keeping LLM’s honest. They’re also a design & refactoring tool, a communication tool, and reduce how many tests you have to write.
Parts
- Part 1 – Branded Types
- Part 2 – Product Types
- Part 3 – Union & Discriminated Unions
- Part 4 – Non-Empty Collections
- Part 5 – Indexed Types
- Part 6 – unknown vs any
- Part 7 – Result
- Part 8 – Schema
- Part 9 – Total Function
- Part 10 – Errors as Values
- Part 11 – Property Tests
- Part 12 – Type Proofs
- Part 13 – Exhaustiveness Checking
- Part 14 – Parse, Don’t Validate
- Part 15 – Anti-Corruption Layer
- Part 16 – Opaque Types
- Part 17 – Maybe
- Part 18 – Smart Constructors
- Part 19 – Pipeline
- Part 20 – Railway Oriented Programming
- Part 21 – Typestate
- Part 22 – Capabilities
- Part 23 – Immutability
- Part 24 – Making Impossible States Impossible
- Part 25 – Type Driven Development: How to do it
- Part 26 – Final Thoughts
Type Proofs allow you to write functions in the type system that assert something is true. Where the type system doesn’t allow u to easily express something like u could in say Scala or Haskell instead TypeScript provides the ability to write your functions to express those facts.


type Assert = T
type Cow = false
// ❌ below line does not compile
type _CowIsTrue = AssertCode language: JavaScript (javascript)
The Assert is a type-level assertion. They’re like unit-test assertions expect(thing).toBe(true), much like Vitest’s type level tests expectTypeOf/assertType. The difference is u can build your own from these low-level Conditional & Map types.
Let’s say u want to create the > and < symbols as types so you can prove an Array or Tuple is in the right order. Let’s make >:
type GreaterThan<
A extends number,
B extends number,
Acc extends unknown[] = []
> =
Acc['length'] extends A ? false :
Acc['length'] extends B ? true :
GreaterThan<A, B, [...Acc, 0]>Code language: JavaScript (javascript)
Ok, recursive weird looking 😜. But like runtime functions, you can create new type functions from other type functions:
// swap B and A for GreaterThan
type LessThan<
A extends number,
B extends number> =
GreaterThan<B, A>Code language: HTML, XML (xml)
Finally, we’ll create an assert to say if the type is true or not:
type Assert<_ extends true> = voidCode language: JavaScript (javascript)
Now we can write type-level tests:
type Eight = 8
type Nine = 9
// ❌ does not compile
type _nope = Assert<GreaterThan<Eight, Nine>>
// ✅ compiles
type _yup = Assert<GreaterThan<Nine, Eight>>Code language: JavaScript (javascript)
Baller.
Caveat: Type Proofs can also mean using @ts-expect-error to practice Type Driven Development. More confusing, you can use the type-level functions above in addition to @ts-expect-error to write type test suites.
Type Proofs can also just be helpful types to help in development. You’ve may have used the native ones like Partial<T> and ReadOnly<T>. You can also build your own. Want all permutations of AWS regions, us-east-2 and us-west-1?
type Regions
= [ 'us-east-2', 'us-west-1' ]
| [ 'us-west-1', 'us-east-2' ]Code language: JavaScript (javascript)
Imagine if you added a 3rd region like eu-west-2; now you have 6 unions w/3 items in the tuple; I ain’t writing that by hand. Instead, build a Permutations type, then go:
type AWSRegions = ['us-east-2', 'us-west-1', 'eu-west-2']
type Regions = Permutations<AWSRegions>Code language: JavaScript (javascript)
Type Proofs help assert your types are doing what you think they are w/additional checks; e.g. ensuring you can only add Money<'USD'> to Money<'USD'>, but fails for Money<'EU'>, that functions were called in the right order for Type State, & evidence logic worked with Phantom Types. They also create helper functions to make more advanced types + custom compiler errors. They don’t compile into your code so don’t affect file size.
Leave a Reply