Newtype Pattern
A technique that wraps an existing type (often a primitive) in a new, distinct type, so the compiler can tell apart values that share a representation but mean different things.
Also known as: Newtype, Newtypes, Branded Types, Opaque Types, Value Classes
Category: Software Development
Tags: software-design, software-development, programming, type-systems, domain-modeling, error-prevention
Explanation
A user ID and an order ID are both integers. A distance in meters and a distance in feet are both floating-point numbers. To the compiler, they're interchangeable. To your business, they're not.
In 1999, NASA lost the Mars Climate Orbiter because one piece of software produced values in pound-force seconds while another expected newton-seconds. Both were just numbers.
The newtype pattern gives each concept its own type, even when the underlying data is identical. `UserId` wraps an integer. `OrderId` wraps an integer too. But you can't pass one where the other is expected anymore.
**How it looks**:
- **Haskell**: `newtype UserId = UserId Int`. The language even has a dedicated keyword, which gave the pattern its name
- **Rust**: `struct UserId(u64);`, a tuple struct with one field
- **Kotlin**: `@JvmInline value class UserId(val value: Long)`
- **Scala 3**: `opaque type UserId = Long`
- **TypeScript**: "branded types", e.g., `type UserId = number & { readonly __brand: "UserId" }`, since the structural type system would otherwise treat both as plain numbers
In most of these languages, the wrapper disappears at compile time. You get the type safety without paying for it at runtime.
**What it gives you**:
- **No more mix-ups**: Swapped arguments and wrong units become compile errors
- **Self-documenting signatures**: `transfer(from: AccountId, to: AccountId, amount: Money)` says far more than three strings and a number
- **A home for rules**: Combined with a smart constructor, the newtype guarantees that every instance is valid (e.g., an `EmailAddress` that was checked once at creation)
- **Controlled operations**: You decide what makes sense. Adding two `Meters` is fine; multiplying two `UserId`s isn't
In Rust, newtypes also solve a practical problem: they let you implement a trait for a type from another crate, which the orphan rule would otherwise block.
**What to keep in mind**:
Wrapping everything can feel heavy at first, especially when you need to unwrap values to call other libraries. Start with the concepts that get mixed up most often: identifiers, units, money and anything that carries validation rules.
The newtype pattern is the most direct fix for Primitive Obsession, and a cheap way to make code Hard to Misuse.
Related Concepts
← Back to all concepts