Typestate Pattern
A design technique that encodes an object's current state in its type, so calling an operation in the wrong state becomes a compile-time error instead of a runtime bug.
Also known as: Typestate, Typestate Analysis, Type State Pattern
Category: Software Development
Tags: software-design, software-development, programming, type-systems, error-prevention
Explanation
Some objects only make sense in a certain order. Open a file, then read it, then close it. Build a request, then send it. Connect, authenticate, then query. Call things in the wrong order, and you get an exception at runtime. Or worse, silent corruption.
The typestate pattern moves those rules into the type system. Each state becomes its own type, and each type only exposes the operations that are valid in that state. Reading a closed file doesn't throw an error. It doesn't compile.
**Where the idea comes from**:
Robert Strom introduced typestate in 1983, and formalized it with Shaula Yemini in a 1986 paper titled "Typestate: A programming language concept for enhancing software reliability". The idea stayed mostly academic for decades. Rust made it practical and popular, because its ownership system can guarantee that an old state can't be used after a transition.
**How it works**:
Take a connection with three states: `Disconnected`, `Connected` and `Authenticated`.
- `Connection<Disconnected>` only has a `connect()` method, which returns a `Connection<Connected>`
- `Connection<Connected>` only has `authenticate()`, which returns a `Connection<Authenticated>`
- `Connection<Authenticated>` is the only one with `query()`
Each transition consumes the old value and returns a new one. There is simply no way to call `query()` on a connection that hasn't authenticated. The compiler checks the whole protocol for you.
**Common uses**:
- **Builders** that require some fields before `build()` becomes available
- **Resources** with a lifecycle (files, sockets, database transactions)
- **Protocols and workflows**: a payment that must be authorized before it can be captured
- **Hardware drivers**, where configuring a pin in the wrong mode can damage a device
**What it costs**:
- More types, more boilerplate, and longer type signatures
- It works best when states change in a known sequence. If the state depends on runtime data (e.g., a server's response), you still need a runtime check at that point, typically with a sum type
- It's most natural in languages with ownership or linear types (Rust). In other languages, nothing stops you from keeping a reference to the old state and reusing it
**How it connects**:
The typestate pattern is a finite-state machine checked at compile time. It's one of the strongest ways to Make Illegal States Unrepresentable: invalid transitions can't be written, so they can't happen.
Related Concepts
← Back to all concepts