Enhanced enums
Until Dart 2.17 an enum was just a list of names. Now an enum is a full type: it can have fields, a const constructor, methods, getters, it can implement interfaces and apply mixins.
The rules:
- The values come first, then a
;, then the rest of the members. - The constructor must be
constand every fieldfinal— enum values are compile-time constants. - An enum cannot
extendsanother class (it implicitly extendsEnum), butimplementsandwithare allowed. - There must be at least one value — an empty enum does not exist.
What this means in practice: the "enum to value" maps that used to be written as switch statements (a code, a colour, a translation key) now live inside the enum itself as fields.
| Member | What it gives | Watch out |
|---|---|---|
| `Role.values` | A `List` of all values, in declaration order | It is unmodifiable; use it freely with `where` and `map` |
| `role.name` | The value's name as a `String` — `'admin'` | `toString()` gives `'Role.admin'` instead — use `name` for serialisation |
| `role.index` | The zero-based ordinal position | Never persist it — reordering the values corrupts stored data |
| `Role.values.byName('admin')` | Looks a value up by name | **Throws ArgumentError** when not found — never feed it raw external data |
| `Role.values.asNameMap()` | A `Map<String, Role>` for safe lookups | `asNameMap()[raw]` returns `null` instead of throwing |
| `compareTo` | By default comparison is by `index` (via helpers; `Enum` itself is not `Comparable`) | Implement `Comparable` when you need a domain ordering |
Exhaustive switching over an enum. An enum's value set is closed, so the compiler checks a switch completely: cover every value and you need no default. The moment someone adds a value, every default-less switch stops compiling — the same benefit sealed classes give, and the strongest property of an enum.
Hence the practical rule: never write `default` over an enum. default turns the exhaustiveness check off and the "we forgot to handle the new status" bug quietly slips into runtime.
Prefer the switch expression form — it returns a value and forces every branch to produce one:
String label(Role r) => switch (r) { Role.admin => 'Admin', ... };
Enum or sealed class? This is now a standard interview question. The difference compresses into one sentence: an enum is a set of constant values, a sealed hierarchy is a set of differently shaped variants.
The deciding criteria:
- Does each variant carry its own data?
CardPayment(last4)andCashPayment()hold different fields → sealed. In an enum every value shares the same field set. - Does the variant set need to grow at runtime? Neither supports that — both are closed.
- Is it just a list of names (
Role,Direction,LogLevel)? → enum. Simpler,valuescomes free, serialisation is easy. - Do you need different behaviour per variant? Both work: a method on the enum, an override in the sealed hierarchy.
One more practical argument: an enum gives you ready-made serialisation through values, byName and index; with a sealed hierarchy you write that yourself. In exchange, a sealed type can be generic (Result<T>) and an enum cannot.
Interview tip. Two traps here, both from real practice.
First: "how do you persist an enum value to a database?" The weak answer is index. The strong answer: never persist `index` — the day someone sorts the values alphabetically, every stored row is read as the wrong value. Persist name (or an explicit code field inside the enum) and parse it back safely with asNameMap().
Second: "can you parse server data with `byName`?" — No: byName throws ArgumentError when there is no match. The correct approach is Role.values.asNameMap()[raw] ?? Role.unknown or firstWhere(..., orElse: ...) — plus deliberately adding an unknown value to the enum. This answer moves you from "knows the language" to "has shipped this to production".
📚 Sources and documentation
- Enumerated typesofficialdart.dev