Type Driven Development: How to Do It

Written by

in

Part 25 – Type Driven Development

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.

Update: Wed, Aug 12, 2026 – updated both examples to compile if you copy paste them.

Parts
  1. Part 1 – Branded Types
  2. Part 2 – Product Types
  3. Part 3 – Union & Discriminated Unions
  4. Part 4 – Non-Empty Collections
  5. Part 5 – Indexed Types
  6. Part 6 – unknown vs any
  7. Part 7 – Result
  8. Part 8 – Schema
  9. Part 9 – Total Function
  10. Part 10 – Errors as Values
  11. Part 11 – Property Tests
  12. Part 12 – Type Proofs
  13. Part 13 – Exhaustiveness Checking
  14. Part 14 – Parse, Don’t Validate
  15. Part 15 – Anti-Corruption Layer
  16. Part 16 – Opaque Types
  17. Part 17 – Maybe
  18. Part 18 – Smart Constructors
  19. Part 19 – Pipeline
  20. Part 20 – Railway Oriented Programming
  21. Part 21 – Typestate
  22. Part 22 – Capabilities
  23. Part 23 – Immutability
  24. Part 24 – Making Impossible States Impossible
  25. Part 25 – Type Driven Development: How to do it
  26. Part 26 – Final Thoughts

Type Driven Development (TyDD) is a lot like Test Driven Development (TDD); a design technique that results in proofs for easy to change code later. In most languages, you’ll use together; more types in a better type system, more tests in a bad one. Here’s 4 ways to approach it.

We’ll be enforcing Secondary Accounts can only cancel their own Transactions, Primary Accounts can cancel any Transaction. I’m Primary, & my daughter, Sydney, is Secondary.

In TDD:

1. write our expectation

expect(
  canCanel(
    { accountType: 'primary', id: 'jesse' },
    { transactionID: '4', accountID: 'jesse' }
  )
).toBe(true)Code language: CSS (css)

2. implement the function in simplest way to make it pass

3. refactor or move into next test

1st: In good type systems, you just write code, fix the red, refactor. Each change that breaks the code, you let the compiler tell you what to fix. You keep the changes small.

2nd: The @ts-expect-error comment directive way allows you to write types and functions normally, but for unhappy paths you must use the comment above them:

1. create a failing type function

// fixtures
type UserID = string & { brand: 'UserID' }
type ProductID = string & { brand: 'ProductID' }

declare const userID: UserID
declare const productID: ProductID

// function
declare function getUser(id: string): void

// failing test; should not compile
getUser(productId)
Code language: PHP (php)

2. simplest function to make it pass

// declare function getUser(id: string): void
// fixed function
declare function getUser(id: UserID): voidCode language: PHP (php)

3. Now your @ts-expect-error comment will no longer be underlined red. Refactor types OR do happy paths without the comment (e.g. cancelPayment(jesseTransaction, jesse) )

Caveat: The comment must be above compiler error; if you multi-line your types like me, you may have to move it.

3rd: The type proof way is similiar:

1. write a failing proof

// fixtures
// same as above

// proof functions
type Assert<_Condition extends true> = void

type isAssignable<From, To> =
  [From] extends [To] ? true : false

type Not<Condition extends boolean> =
  Condition extends true ? false : true

// Unhappy path, failing proof
type _ProductIdCannotBePassedToGetUser = Assert<
  Not<
    IsAssignable<
      ProductID,
      Parameters<typeof getUser>[0]
    >
  >
>


Code language: JavaScript (javascript)

2. make proof pass by improving the type

// old function
// declare function getUser(id: string): void
// new function
declare function getUser(id: UserID): voidCode language: PHP (php)

3. either refactor or move to additional proof tests

The fourth is just to intermingle 1 of the above 3 _while_ you do TDD. That’s what I do.

Part 26 will have final conclusions.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *