TypeScript
State Encoding
Learn state machines with phantom types.
State encoding
Phantom types can be used to encode the stages of a state machine into the type. Each state is represented by its own phantom argument; transition functions accept only values in the correct state. This way invalid transitions become compile errors.
type Order<S> = { amount: number };
type Pending = Order<"pending">;
type Paid = Order<"paid">;
type Shipped = Order<"shipped">;
function pay(o: Pending): Paid { return o; }
function ship(o: Paid): Shipped { return o; }
Transition functions
Transition functions separate input and output types with phantom arguments; for example, pay takes Order<"pending"> and returns Order<"paid">. This ensures the function can only be called in the correct order.
const p: Pending = { amount: 10 };
const paid = pay(p); // Pending -> Paid
const shipped = ship(paid); // Paid -> Shipped
const bad = ship(p); // error: Pending is not Paid
Exercise Order flow with state encoding
Define the Order<S> generic type (body amount: number). Create Pending, Paid, Shipped types with phantom arguments. Write pay(o: Pending): Paid and ship(o: Paid): Shipped functions. Start from a Pending value, apply pay and ship, and print the amount of the result via console.log.
Show example solution
type Order<S> = { amount: number };
type Pending = Order<"pending">;
type Paid = Order<"paid">;
type Shipped = Order<"shipped">;
function pay(o: Pending): Paid {
return o;
}
function ship(o: Paid): Shipped {
return o;
}
const p: Pending = { amount: 10 };
const shipped = ship(pay(p));
console.log(shipped.amount);
Test yourself
-
What is the core advantage of state encoding with phantoms?
- Performing state checks at runtime
- Faster code execution
- Lower memory usage
- Catching invalid state transitions at compile time
Show answer
Catching invalid state transitions at compile time — With phantoms each state is a distinct type, so out-of-order transitions (e.g. shipping a Pending order) become compile errors; no runtime check is needed.
-
Transition function signature
type Order<S> = { amount: number };
type Pending = Order<"pending">;
type Paid = Order<"paid">;
function pay(o: Pending): ____ { return o; }
Show answer
type Order<S> = { amount: number };
type Pending = Order<"pending">;
type Paid = Order<"paid">;
function pay(o: Pending): Paid { return o; }
-
State encoding parts
- Pending durumu
- unpaid order
- Paid durumu
- paid order
- pay fonksiyonu
- takes Pending returns Paid
- ship(p) çağrısı
- compile error (wrong state)
Next lesson: API with phantom