Extension Types in Dart 3.3
Dart 3.3 introduced extension types — a way to wrap an existing type and give it a different interface, with zero runtime overhead.
Why they matter
Before extension types, a common pattern was:
typedef UserId = String;
typedef ProductId = String;
void fetchUser(UserId id) { ... }
// Nothing stops this mistake:
fetchUser(productId); // compiles fine
Both UserId and ProductId are just String at runtime. The compiler can’t help.
Extension types to the rescue
extension type UserId(String value) {}
extension type ProductId(String value) {}
void fetchUser(UserId id) { ... }
fetchUser(ProductId('123')); // compile error!
Now the compiler enforces the distinction. At runtime, it’s still just a String — no
wrapper object, no allocation, no overhead.
Implementing interfaces
extension type Celsius(double value) implements double {
Celsius operator +(Celsius other) => Celsius(value + other.value);
Fahrenheit toFahrenheit() => Fahrenheit(value * 9 / 5 + 32);
}
extension type Fahrenheit(double value) implements double {}
When to use them
- Domain identifiers (
UserId,OrderId,Email) - Unit types (
Meters,Kilograms,Seconds) - Restricted views of existing types (expose only a subset of
Listmethods)
Extension types are a lightweight alternative to full wrapper classes when you only need type safety, not behavioural extension.