ZetCode

Static Members in Dart

last modified October 18, 2026

In this article we explore static members in Dart, the fields, methods, and accessors that belong to a class itself rather than to any instance.

Static Members Overview

Normal members belong to objects: each instance gets its own copy of a field and calls methods on itself through this. A static member, by contrast, belongs to the class. There is one copy of a static field per isolate no matter how many instances exist, and static methods are invoked on the class name rather than on an object.

Statics are useful for values and behaviour that are genuinely class-wide: mathematical constants, pure utility functions, shared counters, caches, and singletons. Because there is no instance involved, a static method cannot access this or an instance member without an object reference. It can access instance members through an object passed as an argument. Static members are not inherited and do not participate in dynamic dispatch.

Member Belongs to Accessed via Example
Instance field Each object obj.field this.name
Static field The class Class.field User.count
Static constant The class Class.constantName MathUtils.pi
Static method The class Class.method() MathUtils.max(1, 2)
Static getter/setter The class Class.prop AppConfig.env

Static Fields and Constants

A static field is declared with the static keyword. When it is also const, its value is fixed at compile time and can be used in other const expressions. A static final field can instead be initialized at runtime, once on first use. final prevents reassignment of the field, not mutation of an object such as a list or map stored in it. Static methods are commonly used for functions that need no instance state; top-level functions are another idiomatic option for general-purpose utilities.

static_fields.dart
class MathUtils {
  static const double pi = 3.141592653589793;

  static int max(int a, int b) => a > b ? a : b;

  static double circleArea(double r) => pi * r * r;
}

void main() {
  print(MathUtils.pi);
  print(MathUtils.max(3, 7));
  print(MathUtils.circleArea(2).toStringAsFixed(2));
}

pi is a class-level constant shared by every caller, and max and circleArea are pure functions that depend only on their arguments. Because none of them touch instance state, they are declared static and are called on the class name. circleArea reuses the pi constant, showing how static members refer to one another without an object.

$ dart run static_fields.dart
3.141592653589793
7
12.57

Shared Mutable State

A static field has a single storage location within an isolate, which makes it useful for shared counters and registries. Every constructor call in that isolate sees and updates the same variable. Other isolates have their own static state; they do not share this counter.

shared_state.dart
class User {
  static int _count = 0;
  static int get count => _count;

  final String name;

  User(this.name) {
    _count++;
  }

  static void reset() => _count = 0;
}

void main() {
  User('a');
  User('b');
  User('c');
  print(User.count);

  User.reset();
  print(User.count);
}

The private static field _count is shared across all User instances. Each constructor increments it, and the static getter count exposes the number constructed since the last reset, not the number of objects still alive. The static reset method sets it back to zero. Because the field is static, User.count is read from the class rather than from any particular user.

$ dart run shared_state.dart
3
0

Static Caches and Factories

Static fields can hold a cache that is shared by all instances. Combined with a factory constructor, this lets a class return a canonical, reused instance for a given value instead of building a new object every time.

static_cache.dart
class Color {
  final int value;

  static final Map<int, Color> _cache = {};

  const Color._(this.value);

  factory Color(int value) =>
      _cache.putIfAbsent(value, () => Color._(value));

  static const Color red = Color._(0xFF0000);

  @override
  String toString() => 'Color(0x${value.toRadixString(16)})';
}

void main() {
  var a = Color(0x00FF00);
  var b = Color(0x00FF00);

  print(a);
  print(identical(a, b));
  print(Color.red);
}

The static map _cache stores one instance per colour value. The factory constructor consults it with putIfAbsent, so the second call for the same value returns the first object. The identical check confirms that a and b are the same instance. The static constant red is a compile-time constant that bypasses the cache.

$ dart run static_cache.dart
Color(0xff00)
true
Color(0xff0000)

Static Members Are Not Virtual

Instance methods are resolved by dynamic dispatch, but static members are resolved on the class where they are declared. A subclass cannot override a static method, and redeclaring a static field with the same name creates a separate field rather than replacing the superclass one.

not_virtual.dart
class Base {
  static String label = 'base';
  static String describe() => 'I am $label';

  String instanceLabel() => label;
}

class Child extends Base {
  static String label = 'child';
}

void main() {
  print(Base.describe());
  print(Child.label);

  Child.label = 'child2';
  print(Base.label);
  print(Child.label);
  print(Child().instanceLabel());
}

Child declares its own static label, but it is a different variable from Base.label. Setting Child.label leaves Base.label unchanged. The static method describe is only declared on Base and reads the Base field, so changing Child.label cannot affect its result. Child.describe() is invalid because static members are not inherited. Even when instanceLabel runs on a Child, its unqualified label reference still resolves to Base.label; that static reference is not dynamically dispatched.

$ dart run not_virtual.dart
I am base
child
base
child2
base

The Singleton Pattern

Because a static field is initialized once per isolate, it can hold a singleton in Dart. A private constructor plus a static final instance provides one shared instance through the public API, not one object across all isolates.

singleton.dart
class Database {
  static final Database instance = Database._();
  static int _connections = 0;

  Database._();

  void connect() {
    _connections++;
    print('connections: $_connections');
  }
}

void main() {
  Database.instance.connect();
  Database.instance.connect();
  print(identical(Database.instance, Database.instance));
}

The private constructor Database._() prevents other libraries from constructing instances; code in the defining library can still call it. The static field instance is created lazily on first access and then reused, so every caller shares the same object and the same _connections counter. The identical check returning true confirms that both accesses return the same object. Static caches and singletons retain their state for the isolate's lifetime, so consider memory growth, test isolation, and explicit dependency injection when designing larger applications.

$ dart run singleton.dart
connections: 1
connections: 2
true

Static Counters for Identity

A static counter is a simple way to hand each instance a distinct numeric ID within one isolate. It is not a globally unique ID or Dart's object identity. The counter lives on the class, while each object captures its own value at construction time.

instance_ids.dart
class Ids {
  static int _next = 0;

  static int next() => ++_next;

  static void reset() => _next = 0;
}

class Session {
  final int id = Ids.next();
  final String user;

  Session(this.user);

  @override
  String toString() => 'Session(#$id, $user)';
}

void main() {
  Ids.reset();
  print(Session('Alice'));
  print(Session('Bob'));
  print(Session('Carol'));
}

Ids.next increments the shared static counter and returns the new value. The Session field initializer final int id = Ids.next() runs once per object, so sessions receive successive IDs from the class-wide sequence. Resetting while existing sessions remain can produce duplicate IDs, and other isolates or process restarts begin their own sequences. The example resets only before creating its sessions to give a predictable demonstration.

$ dart run instance_ids.dart
Session(#1, Alice)
Session(#2, Bob)
Session(#3, Carol)

Source

Dart Language: Static variables
Dart Language: Static methods
Dart Language: Factory constructors
Dart Language: Class Modifiers

Static members belong to the class rather than to instances, giving Dart a convenient home for constants, utility functions, shared state, caches, and singletons within an isolate. They have no implicit instance receiver and are not polymorphic, so they are best reserved for behaviour that is genuinely class-wide rather than used as a substitute for instance members.

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all Dart tutorials.