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.
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.
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.
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.
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.
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.
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
List all Dart tutorials.