| |

Dart 28 🎯 Enums: Basic Enums, Enhanced Enums with Fields, Methods, and Interfaces

An enum is a type with a fixed set of named values. In most languages, that is the entire story: the values are constants, and the compiler knows all of them. Dart’s enums started that way, but Dart 2.17 changed them significantly. An enum value can now have fields, methods, and constructors. It can implement interfaces. It can carry data that differs per value. This makes Dart’s enums more than a list of constants—they are a mechanism for modeling a closed set of alternatives, each with its own data and behavior.

This chapter covers Dart’s enums in full. You will learn the basic form, the enhanced form introduced in Dart 2.17, the fields and methods that each value can have, the interfaces an enum can implement, and the exhaustive switching that enums enable. You will also see the patterns that enhanced enums replace—factory functions, lookup maps, and sealed class hierarchies—and the situations where a sealed class is still the better choice.

Key point: Every enum value is a constant instance of the enum type. In a basic enum, the values are bare names. In an enhanced enum, each value can be constructed with arguments, and the enum can declare fields, methods, and a constructor. The constructor is always private to the enum. The set of values is fixed at compile time, so a switch over an enum can be checked for exhaustiveness.


Why enums exist

The named constant problem. A program needs to represent a fixed set of options: days of the week, order statuses, log levels, payment methods. The naive approach is to use string or integer constants. 'pending', 'shipped', 'delivered'. This works until someone typos the string, or uses a value that is not in the set, or adds a new value and forgets to update the validation. An enum makes the set explicit and the compiler enforces it. A value that is not one of the declared names cannot be assigned to the type.

The exhaustiveness problem. A switch over a set of constants cannot be checked for exhaustiveness. A switch over an enum can. The compiler knows every value the enum has, so it verifies that every case is handled. Adding a new value to the enum turns every incomplete switch into a compile error, which is exactly what is needed when the set of options changes.

The data-per-value problem. Some enums carry data that differs per value. A Plan enum might have a price and a maximum number of users. A HttpStatus enum might have a code and a description. Before Dart 2.17, this required a separate map from enum value to data, or a class hierarchy with a subclass per value. Enhanced enums allow the data to be part of the enum declaration.

The behavior-per-value problem. Some enums have methods that behave differently per value. A Shape enum might have an area method that computes the area differently for each shape. Before enhanced enums, this required a switch inside every method, or a sealed class hierarchy. Enhanced enums allow each value to provide its own implementation of an abstract method.

The sealed class overlap. A sealed class with final subtypes provides the same guarantees as an enhanced enum: a closed set of alternatives, data per alternative, and exhaustive matching. The difference is that enum values are constants and the set is known at compile time, while sealed subclasses are types that can have instances. Use an enum when the alternatives are values; use a sealed class when the alternatives are types with multiple instances.


a. Basic enums

A basic enum declares a set of named values. Each value is a constant instance of the enum type.

enum Direction {
  north,
  south,
  east,
  west,
}

void main() {
  var d = Direction.north;
  print(d); // Direction.north
}

The values are accessed with the enum name and a dot: Direction.north. The enum type can be used in function signatures, switch statements, and type checks.

String describe(Direction d) {
  switch (d) {
    case Direction.north:
      return 'North';
    case Direction.south:
      return 'South';
    case Direction.east:
      return 'East';
    case Direction.west:
      return 'West';
  }
}

The switch is exhaustive. If a case is missing, the compiler reports an error. The default case is not needed—and is discouraged—because the compiler verifies that every value is handled.

Enums have two built-in properties. index is the zero-based position of the value in the declaration order. name is the value’s name as a string.

print(Direction.north.index); // 0
print(Direction.north.name);  // 'north'

The values static property returns a list of all the enum values in declaration order.

print(Direction.values); // [Direction.north, Direction.south, Direction.east, Direction.west]

You can iterate the values and use them for building menus, validating input, or serializing to a list.


b. Enhanced enums: fields and methods

An enhanced enum declares fields, a constructor, and methods. Each value is constructed with arguments that populate the fields.

enum Planet {
  mercury(mass: 3.303e+23, radius: 2.4397e6),
  venus(mass: 4.869e+24, radius: 6.0518e6),
  earth(mass: 5.976e+24, radius: 6.37814e6),
  mars(mass: 6.421e+23, radius: 3.3972e6);

  const Planet({required this.mass, required this.radius});

  final double mass;
  final double radius;

  double get surfaceGravity => 6.67430e-11 * mass / (radius * radius);
}

The values are followed by a semicolon, and then the constructor, fields, and methods. The constructor is const because enum values are constants. The fields are final because the values are immutable. The method surfaceGravity computes a value from the fields.

void main() {
  print(Planet.earth.surfaceGravity); // 9.8...
}

Each value has its own mass and radius, and the method computes the result for that value.

The constructor is always private. An enum cannot be instantiated from outside its declaration. The values are the only instances, and they are created at compile time.

An enhanced enum can have a non-const constructor only if the values are not constant, which is rarely useful. The standard form is const.

Fields can be of any type, including nullable types, collections, and function types. A field can have a default value in the constructor.

enum LogLevel {
  debug(0, prefix: '[DEBUG]'),
  info(1, prefix: '[INFO]'),
  warning(2, prefix: '[WARN]'),
  error(3, prefix: '[ERROR]');

  const LogLevel(this.severity, {required this.prefix});

  final int severity;
  final String prefix;

  bool shouldLog(LogLevel threshold) => severity >= threshold.severity;
}

The severity is a positional argument, and the prefix is a named argument. Each value provides its own. The shouldLog method compares the severity to a threshold.


c. Methods, interfaces, and exhaustiveness

An enhanced enum can declare methods that use its fields. It can also declare abstract methods that each value implements.

enum Shape {
  circle {
    @override
    double area(double size) => 3.14159 * size * size;
  },
  square {
    @override
    double area(double size) => size * size;
  },
  triangle {
    @override
    double area(double size) => 0.5 * size * size;
  };

  const Shape();

  double area(double size);
}

Each value provides its own implementation of the abstract area method. The enum declares the method as abstract, and the values implement it. This is a form of per-value behavior that does not require a switch.

The method can be called on any value:

print(Shape.circle.area(2)); // 12.566...

An enum can implement interfaces. The enum declares implements, and every value must provide the interface’s members.

abstract interface class Describable {
  String describe();
}

enum Priority implements Describable {
  low,
  medium,
  high;

  @override
  String describe() => 'Priority: $name';
}

The Priority enum implements Describable. The describe method uses the name property, which is available on every enum value. Any value can be used where a Describable is expected.

void printDescription(Describable d) {
  print(d.describe());
}

printDescription(Priority.high); // 'Priority: high'

An enum cannot extend a class. It can implement interfaces, but it cannot inherit implementation from a superclass other than the implicit Enum class. The members that the enum needs must be declared in the enum itself or provided by the values.

The switch over an enhanced enum is exhaustive in the same way as a basic enum. The compiler knows the values, so every case must be handled. The values can carry data, and the switch can destructure it:

enum Status {
  active(lastSeen: DateTime.now()),
  inactive;

  const Status({this.lastSeen});
  final DateTime? lastSeen;
}

String describe(Status s) {
  return switch (s) {
    Status.active(lastSeen: var d) => 'Active since $d',
    Status.inactive => 'Inactive',
  };
}

The pattern Status.active(lastSeen: var d) destructures the field. The switch is exhaustive, and the field is available in the case body.

A comparison with sealed classes: a sealed class hierarchy can model alternatives that have different shapes and multiple instances. An enum can model alternatives that are singleton values with data. If the alternatives are “one of a fixed set of options,” an enum is the right choice. If they are “one of a fixed set of types, each with arbitrary instances,” a sealed class is the right choice.


Complete Example Session

// ============================================
// PART 1: BASIC ENUM
// ============================================
enum Direction {
  north,
  south,
  east,
  west,
}

void main() {
  var d = Direction.north;
  print(d.name);  // 'north'
  print(d.index); // 0
}
// ============================================
// PART 2: EXHAUSTIVE SWITCH
// ============================================
String describe(Direction d) {
  return switch (d) {
    Direction.north => 'North',
    Direction.south => 'South',
    Direction.east => 'East',
    Direction.west => 'West',
  };
}
// ============================================
// PART 3: VALUES AND ITERATION
// ============================================
for (var d in Direction.values) {
  print(d.name);
}
// north, south, east, west
// ============================================
// PART 4: ENHANCED ENUM WITH FIELDS
// ============================================
enum Planet {
  mercury(mass: 3.303e+23, radius: 2.4397e6),
  venus(mass: 4.869e+24, radius: 6.0518e6),
  earth(mass: 5.976e+24, radius: 6.37814e6),
  mars(mass: 6.421e+23, radius: 3.3972e6);

  const Planet({required this.mass, required this.radius});

  final double mass;
  final double radius;

  double get surfaceGravity => 6.67430e-11 * mass / (radius * radius);
}
// ============================================
// PART 5: ENHANCED ENUM WITH METHODS
// ============================================
enum LogLevel {
  debug(0, prefix: '[DEBUG]'),
  info(1, prefix: '[INFO]'),
  warning(2, prefix: '[WARN]'),
  error(3, prefix: '[ERROR]');

  const LogLevel(this.severity, {required this.prefix});

  final int severity;
  final String prefix;

  bool shouldLog(LogLevel threshold) => severity >= threshold.severity;

  String format(String message) => '$prefix $message';
}
// ============================================
// PART 6: PER-VALUE METHOD
// ============================================
enum Shape {
  circle {
    @override
    double area(double size) => 3.14159 * size * size;
  },
  square {
    @override
    double area(double size) => size * size;
  };

  const Shape();

  double area(double size);
}
// ============================================
// PART 7: ENUM IMPLEMENTING INTERFACE
// ============================================
abstract interface class Describable {
  String describe();
}

enum Priority implements Describable {
  low,
  medium,
  high;

  @override
  String describe() => 'Priority: $name';
}
// ============================================
// PART 8: ENUM WITH NULLABLE FIELD
// ============================================
enum Status {
  active(lastSeen: DateTime.now()),
  inactive;

  const Status({this.lastSeen});
  final DateTime? lastSeen;
}
// ============================================
// PART 9: DESTRUCTURING ENUM IN SWITCH
// ============================================
String describeStatus(Status s) {
  return switch (s) {
    Status.active(lastSeen: var d) => 'Active since $d',
    Status.inactive => 'Inactive',
  };
}
// ============================================
// PART 10: ENUM AS A MAP KEY
// ============================================
final prices = {
  Priority.low: 0,
  Priority.medium: 10,
  Priority.high: 25,
};

print(prices[Priority.high]); // 25

The ten parts covered basic enums, exhaustive switch, values and iteration, enhanced enums with fields, enhanced enums with methods, per-value methods, enums implementing interfaces, enums with nullable fields, destructuring in switch, and enums as map keys.


Quick Reference

Basic Enum

FeatureSyntax
Declarationenum Name { value1, value2 }
AccessName.value1
IndexName.value1.index
NameName.value1.name
All valuesName.values

Enhanced Enum

FeatureSyntax
Values with argsvalue(arg1, arg2)
Constructorconst Name(this.field)
Fieldsfinal Type field;
MethodsType method() { }
Abstract methodType method();
Per-value implementationvalue { @override ... }

Enum Rules

RuleRequirement
ConstructorMust be const (or factory)
FieldsMust be final
InheritanceCannot extend a class
InterfacesCan implement
MixinsCannot use
InstantiationOnly the declared values

Enum vs Sealed Class

AspectEnumSealed Class
ValuesFixed constantsTypes with instances
Data per valueYes (enhanced)Yes
Exhaustive switchYesYes
Multiple instancesNoYes
Pattern matchingYesYes
Use caseFixed optionsAlternative types

Best Practices

✅ Do This:

// Use enums for fixed sets of options
enum Direction { north, south, east, west }                   // ✅

// Use switch for exhaustive handling
switch (d) {
  case Direction.north: // ...
}                                                             // ✅

// Use enhanced enums for data per value
enum Planet {
  earth(mass: 5.976e+24);
  const Planet({required this.mass});
  final double mass;
}                                                             // ✅

// Use per-value methods for behavior
enum Shape {
  circle { double area() => ...; };
  double area();
}                                                             // ✅

// Use enums as map keys
final prices = {Priority.low: 0, Priority.high: 25};          // ✅

// Implement interfaces when needed
enum Priority implements Describable { }                      // ✅

❌ Don’t Do This:

// Don't use string constants instead of enums
const statusPending = 'pending';                              // ❌

// Don't use a default case on an exhaustive switch
switch (d) {
  case Direction.north: // ...
  default: // hides new values                                // ❌
}

// Don't make enum fields mutable
enum Bad {
  value;
  int count = 0;  // must be final                           // ❌
}

// Don't try to extend an enum
class MyEnum extends Direction { }  // error                  // ❌

// Don't instantiate an enum
var d = Direction();  // error                                // ❌

// Don't use an enum when values need multiple instances
// Use a sealed class instead                                   // ⚠️

Common Pitfalls

PitfallWhy It HappensFix
Missing semicolonEnhanced enum needs ; after valuesAdd ; before the constructor
Non-const constructorEnum values are constantsUse const constructor
Non-final fieldEnum values are immutableUse final
Cannot extend enumEnums cannot be extendedUse interfaces or sealed classes
Switch not exhaustiveMissing caseHandle every value
Default hides new valuesUsed defaultRemove default
Index changesReordered valuesUse name instead of index

Real-World Examples

1. Order Status

enum OrderStatus {
  pending,
  processing,
  shipped,
  delivered,
  cancelled,
}

2. HTTP Status with Code

enum HttpStatus {
  ok(200, 'OK'),
  notFound(404, 'Not Found'),
  serverError(500, 'Internal Server Error');

  const HttpStatus(this.code, this.message);
  final int code;
  final String message;
}

3. Log Level

enum LogLevel {
  debug(0),
  info(1),
  warning(2),
  error(3);

  const LogLevel(this.severity);
  final int severity;
}

4. Plan with Price

enum Plan {
  free(price: 0, users: 1),
  pro(price: 10, users: 10),
  enterprise(price: 100, users: 1000);

  const Plan({required this.price, required this.users});
  final int price;
  final int users;
}

5. Shape with Per-Value Area

enum Shape {
  circle { double area(double s) => 3.14159 * s * s; },
  square { double area(double s) => s * s; };
  double area(double size);
}

6. Payment Method

enum PaymentMethod {
  creditCard,
  debitCard,
  paypal,
  bankTransfer,
}

7. Weekday

enum Weekday {
  monday,
  tuesday,
  wednesday,
  thursday,
  friday,
  saturday,
  sunday;

  bool get isWeekend => this == saturday || this == sunday;
}

8. Sort Order

enum SortOrder { ascending, descending }

9. Environment

enum Environment {
  development(apiUrl: 'http://localhost:3000'),
  staging(apiUrl: 'https://staging.example.com'),
  production(apiUrl: 'https://api.example.com');

  const Environment({required this.apiUrl});
  final String apiUrl;
}

10. Enum to JSON

enum Status {
  active,
  inactive;

  String toJson() => name;

  static Status fromJson(String value) =>
      Status.values.firstWhere((s) => s.name == value);
}

Visual

Basic vs Enhanced Enum

┌─────────────────────────────────────────────────────────────┐
│  BASIC ENUM                                                 │
│                                                             │
│  enum Direction {                                           │
│    north, south, east, west                                 │
│  }                                                          │
│                                                             │
│  Values are bare names.                                     │
│  No data, no methods.                                       │
│                                                             │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ENHANCED ENUM                                              │
│                                                             │
│  enum Planet {                                              │
│    earth(mass: 5.976e+24, radius: 6.37814e6);               │
│                                                             │
│    const Planet({required this.mass, required this.radius});│
│                                                             │
│    final double mass;                                       │
│    final double radius;                                     │
│                                                             │
│    double get surfaceGravity => ...;                        │
│  }                                                          │
│                                                             │
│  Values carry data. Fields, constructor, methods.           │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Exhaustive Switch

┌─────────────────────────────────────────────────────────────┐
│  enum Status { active, inactive, pending }                  │
│                                                             │
│  switch (status) {                                          │
│    case Status.active: ...                                  │
│    case Status.inactive: ...                                │
│    case Status.pending: ...                                 │
│  }                                                          │
│  ✓ Exhaustive                                               │
│                                                             │
│  switch (status) {                                          │
│    case Status.active: ...                                  │
│    case Status.inactive: ...                                │
│  }                                                          │
│  ✗ Error: pending not handled                               │
│                                                             │
│  Adding a new value breaks all incomplete switches.         │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Per-Value Methods

┌─────────────────────────────────────────────────────────────┐
│  enum Shape {                                               │
│    circle {                                                 │
│      @override                                               │
│      double area(double s) => 3.14159 * s * s;              │
│    },                                                       │
│    square {                                                 │
│      @override                                               │
│      double area(double s) => s * s;                        │
│    };                                                       │
│                                                             │
│    double area(double size);                                │
│  }                                                          │
│                                                             │
│  Shape.circle.area(2) → 12.566                              │
│  Shape.square.area(2) → 4                                   │
│                                                             │
│  Each value provides its own implementation.                │
│  No switch required.                                        │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Enum vs Sealed Class

┌─────────────────────────────────────────────────────────────┐
│  ENUM                                                       │
│                                                             │
│  Fixed set of singleton values.                             │
│  enum Color { red, green, blue }                            │
│                                                             │
│  Color.red is a constant.                                   │
│  Cannot create another instance.                            │
│                                                             │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  SEALED CLASS                                               │
│                                                             │
│  Fixed set of types, each with instances.                   │
│  sealed class Shape {}                                      │
│  final class Circle extends Shape { double radius; }        │
│                                                             │
│  Circle(2.0) and Circle(3.0) are different instances.       │
│  Can create many instances of each subtype.                 │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Summary

ItemValue
Basic enumenum Name { value1, value2 }
Access valueName.value1
IndexName.value1.index
NameName.value1.name
All valuesName.values
Enhanced enumValues with arguments
Constructorconst Name(this.field)
Fieldsfinal Type field;
MethodsRegular methods
Abstract methodsPer-value implementation
Interfacesenum Name implements Interface
Exhaustive switchYes

Key takeaways:

  • An enum is a fixed set of named values. The compiler knows every value, so a switch over an enum is checked for exhaustiveness. Adding a new value breaks every incomplete switch at compile time.
  • Every enum value has an index and a name. The index is the zero-based position in the declaration order. The name is the value’s identifier as a string. The values static property returns all values in order.
  • Enhanced enums carry data per value. Each value can be constructed with arguments that populate fields. The constructor is always private, and the fields are final. The values are the only instances of the enum.
  • Enhanced enums can have methods and implement interfaces. A method can use the enum’s fields to compute a result. An abstract method can be implemented per value, with each value providing its own behavior.
  • The switch over an enhanced enum can destructure fields. A pattern like Status.active(lastSeen: var d) extracts the field and binds it to a variable in the case body.
  • Enums cannot be extended or instantiated. They can implement interfaces, but they cannot extend a class or use mixins. The set of values is fixed at compile time.
  • Enums are ideal for fixed sets of options; sealed classes are ideal for alternative types. Use an enum when the alternatives are singleton values. Use a sealed class when the alternatives are types that can have multiple instances with different data.

Remember: Dart’s enums are more than a list of constants. An enhanced enum can carry data, define methods, and implement interfaces, while preserving the exhaustiveness guarantee that makes enums useful for pattern matching. The values are constants, the constructor is private, and the fields are final. Use enums for the fixed sets of options that appear everywhere in real programs: order statuses, log levels, payment methods, plans, and directions. Use enhanced enums when each option needs data or behavior. Use sealed classes when the alternatives are types rather than values. And use switch without a default case, so the compiler can tell you when a new value needs to be handled.



Stop using slow, ad-bloated tool sites! 🤮

🔎 Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)

⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free

🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!