skip to content

Enhanced Enums

Dart enums expose index, name and a values list, and enhanced enums add fields, const constructors, methods and interfaces. Interviewers ask when an enhanced enum beats a class of constants.

part ofDartoverview, primer and where to startread it →
on this pageshow

explore

questions

5

In Dart, what do an enum value's index and name return, and how do you list all values and look one up from a string?

level: juniorimportance: must knowfreq 55%

answer

  1. zero-based declaration position
  2. the source identifier as a String
  3. a static values list
  4. values.byName throws on a miss
  5. asNameMap for a nullable lookup

basics

~20 s

index is the value's zero-based position in the declaration and name is its source identifier as a String. The generated static values list holds every value in order; values.byName('x') finds one by name and throws ArgumentError when none matches.

solid answer

~40 s

Every Dart enum value has `index`, its zero-based position in declaration order, and `name`, the identifier it was declared with, so `OrderStatus.shipped.name` is `'shipped'`. `toString()` returns the qualified form, `'OrderStatus.shipped'`. The compiler generates a static `values` list containing all values in declaration order, so `OrderStatus.values[i].index == i`. To go from a string back to a value, `OrderStatus.values.byName('shipped')` scans the list and throws an `ArgumentError` if nothing matches; `values.asNameMap()` builds a `Map<String, OrderStatus>` whose lookup returns `null` for an unknown name, which suits untrusted input. `name` is an extension getter, `EnumName.name`, which is why an enum may still declare its own member called `name`. Flutter's old `describeEnum` helper is deprecated in favour of `.name`.

code

dart · 17 lines
dart
enum OrderStatus { pending, shipped, delivered, cancelled }

void main() {
  print(OrderStatus.shipped.index); // 1
  print(OrderStatus.shipped.name); // shipped
  print(OrderStatus.shipped); // OrderStatus.shipped
  print(OrderStatus.values.length); // 4

  print(OrderStatus.values.byName('delivered')); // OrderStatus.delivered
  print(OrderStatus.values.asNameMap()['lost']); // null

  try {
    OrderStatus.values.byName('lost');
  } on ArgumentError catch (e) {
    print('no such status: ${e.invalidValue}');
  }
}

go deeper

for a junior

Recall index as the declaration position, name as the identifier string, and values as the generated list, and know that byName throws on a miss.

for a middle

Explain why name is an extension getter, how asNameMap gives a null-returning lookup, and how Enum.compareByIndex and compareByName sort values.

for a senior

Flag index-based persistence and byName on untrusted input as production risks, and choose lookups with explicit fallbacks.

for a principal

Set conventions for how enums cross boundaries such as storage, APIs and analytics, so identifiers stay stable as the enum evolves.

## What every enum value carries A Dart `enum` declares a type with a **fixed set of constant instances**. Even the simplest form gives each value a few members for free: ```dart enum OrderStatus { pending, shipped, delivered, cancelled } ``` | Member | Example | Result | |---|---|---| | `index` | `OrderStatus.shipped.index` | `1` | | `name` | `OrderStatus.shipped.name` | `'shipped'` | | `toString()` | `OrderStatus.shipped.toString()` | `'OrderStatus.shipped'` | | `values` (static) | `OrderStatus.values` | `[pending, shipped, delivered, cancelled]` | - **`index`** is the value's **zero-based position in the declaration**. The first value is `0`, the next `1`, and so on. It is also the value's position in `values`. - **`name`** is the **source identifier** as a `String`. It is declared as an extension getter, `EnumName.name` in `dart:core`, rather than an instance member, so that an enum can declare its own field or getter called `name` without a conflict. - **`toString()`** includes the enum type: `'OrderStatus.shipped'`. Code that needs just the identifier should use `name`, not string-split `toString()`. - **`values`** is a static list the compiler generates, in declaration order. An enum cannot declare its own member called `values`. ## From a string back to a value `dart:core` adds two extension members, `EnumByName`, on any `Iterable` of enum values, intended for `values`: 1. **`byName(String name)`** walks the list and returns the first value whose `name` matches. If none does, it **throws an `ArgumentError`**. Use it when a miss is a programming error, such as a name you wrote in code. 2. **`asNameMap()`** builds a `Map<String, T>` from names to values. Indexing the map with an unknown name returns **`null`** instead of throwing, which suits data from a server, a deep link or local storage. The `byName` documentation itself suggests caching the map in a static field if lookups are frequent, since `byName` is a linear scan. For a one-off lookup with a fallback you can also write `OrderStatus.values.asNameMap()[raw] ?? OrderStatus.pending`. ## Ordering and comparison Enum values compare with `==` by identity; each value is a canonical constant, so there is exactly one `OrderStatus.shipped` in the program. For sorting, `Enum.compareByIndex` orders by declaration position and `Enum.compareByName` orders alphabetically by name (case-sensitive). A plain enum does not implement `Comparable`, so `list.sort()` without a comparator does not work on it; pass one of those comparators instead. ## Where these members show up in Flutter code - **Dropdowns and filter chips**: `OrderStatus.values.map(...)` builds one menu item or chip per value, and the list updates automatically when a value is added. - **Route and query parameters**: a `?status=shipped` parameter arrives as a `String`; `asNameMap()` turns it back into a value without crashing on a typo. - **Logging**: `status.name` gives a compact identifier for log lines, while `toString()` gives the qualified form. - **Ordering**: sorting a list of orders by status in declaration order is `orders.sort((a, b) => Enum.compareByIndex(a.status, b.status))`. ## Legacy you will still see - **`describeEnum(value)`** from Flutter's `foundation` library returned the part after the dot. It is **deprecated** in favour of the `name` getter. - Hand-written `toString().split('.').last` is the pre-`name` workaround, and it breaks if an enum overrides `toString`. - `name`, `byName`, `asNameMap` and the comparators arrived in Dart 2.15, so any Dart 3 codebase has them. ## Common mistakes - Treating `index` as a stable identifier. It changes whenever someone inserts or reorders values, so it is a poor choice to persist or send over the network. - Using `byName` on untrusted input and letting the `ArgumentError` crash a screen. - Expecting `name` to be a human-readable label. It is the code identifier, such as `'inTransit'`; user-facing text belongs in a field of an enhanced enum or in localized strings.

  • Why is name an extension getter rather than an ordinary member of Enum?
    If `name` were an instance member of every enum, an enum could not declare its own field or getter called `name` without clashing with it. Declaring it as the `EnumName` extension in `dart:core` means a member with that name on the enum itself simply wins, while every other enum still gets `.name` for free.
  • A server may send a status string your app version does not know. Which lookup do you use?
    Not `byName`, which throws an `ArgumentError` for an unknown name. Use `OrderStatus.values.asNameMap()[raw]`, cached in a static field if it runs often, and fall back to a default such as a dedicated `unknown` value when it returns `null`. That keeps an older app working when the backend adds a new status.

saying these in an interview costs you the question

  • An enum value's name returns 'OrderStatus.shipped', including the type.
  • values.byName returns null when no value matches.
  • index is a stable ID that is safe to persist forever.
  • You still need describeEnum to get an enum value's name.
  • Plain enums implement Comparable, so values sort without a comparator.
open as a page

In Dart, how do you declare an enhanced enum, such as an order status with a label and a colour, and what rules apply?

level: middleimportance: must knowfreq 50%

basics

~20 s

List each value with constructor arguments, end the list with a semicolon, then declare final fields and a const constructor, plus any getters or methods. Enhanced enums, since Dart 2.17, cannot extend classes or override index, == or hashCode.

open as a page

In Dart, when should you use an enhanced enum instead of a class with static const instances, and when is neither the right fit?

level: middleimportance: should knowfreq 42%

basics

~20 s

Use an enhanced enum for a fixed set of same-shaped values: you get values, byName, a closed type and exhaustive switches. Keep a class of constants when the set must stay open; use a sealed hierarchy when cases carry different data.

open as a page

After a teammate inserted a new value into a Dart OrderStatus enum, orders saved on devices reloaded with the wrong status; what went wrong, and how should enums be persisted?

level: seniorimportance: should knowfreq 36%

basics

~20 s

The app stored index, which is only the declaration position, so inserting a value shifted every later index. Persist a stable identifier instead, such as name or an explicit code field, and map unknown or missing codes to a fallback value.

open as a page

In Dart, how can an enum implement interfaces and apply mixins, and how do you write generic code bounded by Enum?

level: seniorimportance: nice to knowfreq 22%

basics

~20 s

An enum can implement interfaces such as Comparable and apply mixins whose fields are final. Generic code uses T extends Enum to read index and name, but must receive the values list as an argument, since T.values does not compile.

open as a page