skip to content

Extension Methods & Types

Extension methods add members to an existing type with static dispatch, and extension types wrap a representation type at zero runtime cost. Interviewers ask why an extension never works on dynamic.

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

explore

questions

5

In Dart, what is an extension method, how do you declare one with `extension ... on`, and what may it contain?

level: juniorimportance: must knowfreq 60%

answer

  1. add members without editing the type
  2. this is the receiver
  3. methods, getters, setters, operators
  4. no fields, no constructors
  5. import the library to use it

basics

~20 s

An extension method adds a member to an existing type, including types you do not own, via extension Name on Type { ... }. It may declare methods, getters, setters, operators and static members, but no instance fields, constructors or abstract members.

solid answer

~40 s

An extension adds members to a type without changing or subclassing it: `extension NumberParsing on String { int parseInt() => int.parse(this); }` makes `'42'.parseInt()` compile wherever the declaring library is imported. Inside the body, `this` is the receiver. Extensions may declare methods, getters, setters, operators, and static fields and methods. They may not declare instance fields, constructors, abstract members, or members named like one of `Object`'s (`toString`, `==`, `hashCode`). An extension can be generic (`extension MyFancyList<T> on List<T>`) and can target a nullable type such as `String?`, in which case it can be called on `null`. The name is optional, but an unnamed extension is visible only in its own library.

code

dart · 19 lines
dart
extension NumberParsing on String {
  int parseInt() => int.parse(this);
}

extension BlankCheck on String? {
  bool get isNullOrBlank => this == null || this!.trim().isEmpty;
}

extension on List<int> {
  // unnamed: visible only in this library
  int get total => fold(0, (a, b) => a + b);
}

void main() {
  print('42'.parseInt());   // 42
  String? name;
  print(name.isNullOrBlank); // true
  print([1, 2, 3].total);    // 6
}

go deeper

for a junior

Recall the extension Name on Type syntax, that this is the receiver, and that you must import the declaring library to use it.

for a middle

Explain the allowed members, why instance fields and constructors are impossible, and how generic and nullable on types behave.

for a senior

Keep exported extensions small, named and stateless, and avoid scattering overlapping helpers that invite conflicts across packages.

for a principal

Set conventions on where extensions live and which types a shared package may extend, so helpers stay discoverable without polluting common types.

## Why extension methods exist You often want a convenience on a type you cannot change: `String` from `dart:core`, a model class from a generated package, `BuildContext` from Flutter. Subclassing is impossible or pointless (you would have to create your own subclass instances), and a top-level helper function (`parseInt(s)`) reads worse than `s.parseInt()`. Dart's **extension methods** solve this by letting a library add members to an existing type, visible to any code that imports that library. ## Declaring an extension The syntax, per dart.dev: ```dart extension NumberParsing on String { int parseInt() => int.parse(this); double parseDouble() => double.parse(this); } ``` - `extension` introduces the declaration; **`NumberParsing`** is its optional name. - **`on String`** is the **on type**: the static type the members attach to. - Inside the body, **`this`** (often implicit) is the receiver — the string the method was called on. Usage is just a normal member call once the library is imported: ```dart import 'string_apis.dart'; void main() { print('42'.parseInt()); // 42 } ``` IDEs suggest extension members in code completion alongside the type's own members. ## What an extension may contain | Allowed | Not allowed (analyzer message) | |---|---| | instance methods | instance fields ("Extensions can't declare instance fields") | | getters and setters | constructors ("Extensions can't declare constructors") | | operators, e.g. `operator -()` | abstract members ("Extensions can't declare abstract members") | | static fields and static methods | members named like `Object`'s, e.g. `toString`, `hashCode` | | type parameters on the extension | a `super` call (an extension has no superclass) | The no-fields rule is the big one: an extension **cannot store per-object state**. If you need state, you need a class, a mixin, or an `Expando`. ## Generic and nullable on types Extensions can be **generic**; the type parameter is bound from the static type of the receiver: ```dart extension MyFancyList<T> on List<T> { int get doubleLength => length * 2; List<List<T>> split(int at) => [sublist(0, at), sublist(at)]; } ``` They can also target a **nullable** type, which makes them callable on `null` without `?.`: ```dart extension BlankCheck on String? { bool get isNullOrBlank => this == null || this!.trim().isEmpty; } String? name; print(name.isNullOrBlank); // true ``` ## Named versus unnamed The name is optional: ```dart extension on String { bool get isBlank => trim().isEmpty; } ``` An **unnamed extension** is visible **only in the library that declares it**, and, having no name, cannot be applied explicitly to settle a conflict. Use unnamed extensions for private helpers inside one file, and name every extension you export. ## Where interviewers go next 1. Why the call fails on a `dynamic` receiver (static resolution). 2. What happens when the type already has a member of the same name (the real member wins). 3. How to resolve two extensions defining the same member. 4. How extension methods differ from **extension types**, which create a new static type instead of adding members to every instance. ## Typical Flutter uses Extensions are common in Flutter code for small, stateless conveniences: formatting a `DateTime`, turning a `Duration` into a label, or shortening a theme lookup. They must stay stateless and side-effect-light; anything that needs to remember a value between calls belongs elsewhere.

  • In Dart, why can't an extension declare an instance field?
    An extension does not create objects or change their layout; it only adds statically resolved members to an existing type. There is nowhere to store a per-receiver value, so the analyzer rejects instance fields. Static fields are allowed because they belong to the extension declaration, not to each receiver.
  • In Dart, can an extension on String? be called on a null value?
    Yes. When the on type is nullable, `this` inside the extension can be `null`, and a call such as `name.isNullOrBlank` compiles on a `String?` without `?.`. The extension body must handle the null case itself.

saying these in an interview costs you the question

  • Extensions can add instance fields to an existing class.
  • An extension creates a subclass of the on type.
  • Extensions can override toString or == for a type.
  • An unnamed extension is visible to every importing library.
  • Extension methods only work on classes you wrote yourself.
open as a page

In Dart, why does calling an extension method on a `dynamic` variable fail, and what does static resolution mean for overriding?

level: middleimportance: must knowfreq 50%

basics

~20 s

Dart picks an extension member from the receiver's static type at compile time. A dynamic receiver has no static type to match, so the call is dispatched at runtime and throws NoSuchMethodError. For the same reason extensions never override and cannot be overridden.

open as a page

In Dart, when two imported extensions both define the same member for a type, how is the conflict resolved?

level: middleimportance: should knowfreq 30%

basics

~20 s

If both extensions apply, Dart picks the one whose on type is more specific; if neither is, the implicit call is a compile-time ambiguity error. Fix it by applying one explicitly, NumberParsing('42').parseInt(), by limiting an import, or with an import prefix when the extension names also clash.

open as a page

In Dart, how would you use an extension type so user ids and order ids, both plain ints, cannot be mixed up, and what does it cost at runtime?

level: seniorimportance: should knowfreq 30%

basics

~20 s

Declare extension type UserId(int value) {} and extension type OrderId(int value) {}. Each is a distinct static type over int, so passing a UserId where an OrderId or int is expected is a compile error, yet at runtime both are just int: no wrapper object.

open as a page

In Dart, what do `is`, `as` and pattern matches check when a value has an extension type, and why is that weaker than a wrapper class?

level: seniorimportance: nice to knowfreq 18%

basics

~20 s

Extension types are erased, so is, as, switch and if-case checks test the representation object's runtime type. UserId(7) is int and 7 is UserId are both true, and 7 as UserId skips any constructor validation — protection that exists only at compile time.

open as a page