What is a factory constructor?
3 min read
OOP in Dart
| Regular constructor | Factory constructor | |
|---|---|---|
| Creates a fresh instance | ✅ Always | ❓ Up to you |
| Can return an existing object | ❌ | ✅ |
| Can return a subtype | ❌ | ✅ |
Has access to this | ✅ | ❌ |
Can be const | ✅ | ✅ (since Dart 2.0, with restrictions) |
Can be async | ❌ | ❌ (use a static method instead) |
Mental model: a regular constructor is new. A factory constructor is a static method dressed up as a constructor, with one rule — it must return an instance of the declaring type.
Code in action
// 1️⃣ Singleton
class Logger {
Logger._(); // private constructor
static final Logger _instance = Logger._();
factory Logger() => _instance; // always returns the same one
}
identical(Logger(), Logger()); // true
// 2️⃣ Pick a subtype from input
abstract class Shape {
factory Shape(String kind) => switch (kind) {
'circle' => Circle(),
'square' => Square(),
_ => throw ArgumentError('Unknown: $kind'),
};
double get area;
}
// 3️⃣ JSON deserialisation — the most common use in Flutter apps
class User {
final String id, name;
User({required this.id, required this.name});
factory User.fromJson(Map<String, dynamic> json) =>
User(id: json['id'] as String, name: json['name'] as String);
}
// 4️⃣ Instance cache
class Country {
final String code;
Country._(this.code);
static final _cache = <String, Country>{};
factory Country(String code) =>
_cache.putIfAbsent(code, () => Country._(code));
}
identical(Country('IN'), Country('IN')); // true
When to reach for a factory
| Situation | Why |
|---|---|
| Singleton (logger, DB client, analytics) | Return the same instance every call |
fromJson / fromMap / fromBytes | Parsing logic before construction |
| Pick a subtype based on input | Hide concrete classes behind one entry point |
| Cache expensive instances by key | Avoid redundant allocation/setup |
| Validate before constructing (and throw on bad input) | Regular constructors can't return |
If you don't need any of the above, a regular constructor is simpler — don't reach for factory by default.
Common mistakes to avoid
// ❌ Using factory when a regular constructor would do
class Point {
final int x, y;
factory Point(int x, int y) => Point._(x, y); // pointless indirection
Point._(this.x, this.y);
}
// ✅ Just write a regular constructor
// ❌ Forgetting that factory has no `this`
class User {
final String id;
factory User.empty() {
this.id = ''; // ❌ no `this` here
return User._('');
}
User._(this.id);
}
// ❌ Trying to make a factory async
factory User.fromApi() async { ... } // ❌ not allowed
// ✅ Use a static method instead
static Future<User> fromApi() async { ... }
// ❌ Returning a totally unrelated type
abstract class Shape {
factory Shape() => 42; // ❌ must return Shape (or subtype)
}
// ❌ Singleton without a private constructor — anyone can still call `new`
class Logger {
static final _i = Logger();
factory Logger.instance() => _i;
Logger(); // ❌ public — bypasses the factory
}
// ✅ Make the real constructor private (Logger._())
Interview follow-ups
-
What's the difference between a factory constructor and a static method that returns an instance? Functionally similar. Factory constructors are invoked like constructors (
User.fromJson(...),const Foo()), can participate inconstevaluation, and the type system treats the result as the declared type. Static methods are more flexible (e.g.async) but feel less idiomatic for instance creation. -
Can a factory constructor be
const? Yes, if it returns a compile-time constant — typically aconstinstance of the same class. The factory body has to be a singlereturnof a const expression. Useful for canonicalising values. -
How does a factory help implement the Singleton pattern correctly in Dart? You pair it with a private constructor (
Logger._()) and astatic finalinstance. The factory hides instantiation, and the private constructor prevents anyone outside the file from callingnewdirectly. -
Why can't a factory constructor be async? Constructors must return the type synchronously.
asyncwould force the return type to beFuture<T>, which doesn't match the constructor contract. The Dart-idiomatic solution is a static method that returns aFuture<T>.
How helpful was this content?
Please sign in to rate this article.