skip to content

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%

answer

  1. chosen at compile time
  2. static type, not runtime type
  3. dynamic: NoSuchMethodError
  4. instance member always wins
  5. no virtual dispatch, no override

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.

solid answer

~40 s

Extension members are resolved against the **static type** of the receiver, which is why dart.dev says they are as fast as calling a static function. With `dynamic d = '2'; d.parseInt();` the compiler has no static type to match, so it emits an ordinary dynamic call, and at runtime `String` has no `parseInt`: `NoSuchMethodError`. `var v = '2';` works because `v` is inferred as `String`. Static resolution also means: a real instance member of the static type always beats an extension member with the same name; an extension on a subtype cannot override one on a supertype for a value typed as the supertype (`Object o = 'hi'; o.shout()` uses the `Object` extension); and subclasses cannot override extension members. There is no virtual dispatch at all.

code

dart · 20 lines
dart
extension ShoutObject on Object {
  String shout() => 'object';
}

extension ShoutString on String {
  String shout() => 'string';
}

void main() {
  Object o = 'hi';
  print(o.shout());    // object: static type Object picks ShoutObject
  print('hi'.shout()); // string: String is the more specific on type

  dynamic d = 'hi';
  try {
    d.shout();         // no static type, so a dynamic call
  } on NoSuchMethodError {
    print('NoSuchMethodError'); // String has no instance member shout
  }
}

go deeper

for a junior

Remember that extension methods do not work on dynamic values and that the class's own method always wins over an extension.

for a middle

Explain resolution by static type, show how upcasting changes which extension runs, and why a dynamic receiver ends in NoSuchMethodError at runtime.

for a senior

Spot dynamic receivers from decoded JSON in review, and avoid extension names that a future version of the extended type might add.

for a principal

Decide when a shared behaviour needs runtime polymorphism and therefore a real member, rather than an extension that silently depends on static types.

## Two kinds of member lookup Dart has two ways to decide which code a call like `x.shout()` runs: - **Instance members** use **virtual (dynamic) dispatch**: the object's runtime class decides, so a subclass override wins even when `x` is typed as the superclass. - **Extension members** use **static resolution**: the compiler looks at the **static type** of `x`, finds an applicable extension, and turns the call into what is effectively a static function call with `x` as an argument. dart.dev puts it directly: extension methods are resolved against the static type of the receiver, and because of that they are as fast as calling a static function. ## Why `dynamic` breaks it ```dart import 'string_apis.dart'; // extension NumberParsing on String { int parseInt() ... } dynamic d = '2'; print(d.parseInt()); // Runtime exception: NoSuchMethodError var v = '2'; // inferred as String print(v.parseInt()); // 2 ``` The static type of `d` is `dynamic`. The compiler cannot match an `on String` extension against `dynamic`, so it compiles a dynamic call. At runtime the `String` object has no instance member named `parseInt`, and the call throws. Nothing is reported at compile time, which is what makes this a classic interview question. The fix is to give the receiver a static type: declare it `String`, cast it (`(d as String).parseInt()`), or apply the extension explicitly (`NumberParsing(d).parseInt()`, which also type-checks the argument). Common places a receiver silently becomes `dynamic`: 1. Values pulled out of decoded JSON (`jsonDecode` returns `dynamic`), such as `json['name'].toTitleCase()`. 2. Untyped `Map` or `List` literals in older code. 3. Parameters or variables written without a type where inference cannot help. ## What static resolution means for overriding Because the choice is made at compile time from the static type, three consequences follow. **The instance member always wins.** If the static type already declares a member with that name, the extension is not applicable, so the extension member is simply never chosen: ```dart extension Trimmer on String { String trim() => 'extension'; // legal, but never used by 'x'.trim() } ``` **Static type, not runtime type, picks the extension:** ```dart extension ShoutObject on Object { String shout() => 'object'; } extension ShoutString on String { String shout() => 'string'; } void main() { Object o = 'hi'; print(o.shout()); // object (static type Object) print('hi'.shout()); // string (String is the more specific on type) } ``` Both extensions apply to a `String` receiver; the one whose on type is **more specific** (`String` is a subtype of `Object`) wins. For `o`, only `ShoutObject` applies to the static type `Object`, so the runtime string makes no difference. **Extensions cannot be overridden or override.** A subclass cannot override an extension member, and an extension cannot replace a class's method for existing callers. If you need runtime polymorphism, you need an instance member (in a class or a mixin). ## Comparison | Aspect | Instance member | Extension member | |---|---|---| | Chosen by | runtime type of the object | static type of the expression | | Works on `dynamic` receiver | yes (checked at runtime) | no: `NoSuchMethodError` at runtime | | Can be overridden in a subclass | yes | no | | Same name as an instance member | — | instance member wins | | Cost of the call | virtual call | like a static function call | ## Interview framing Say "extension calls are syntactic sugar for static function calls chosen by the static type", then derive the three consequences: `dynamic` fails, the real member wins, and upcasting changes which extension runs. Mention that decoded JSON is where Flutter code most often hits the `dynamic` trap.

  • In Dart, how can you call an extension member when the value you hold is typed dynamic?
    Give the compiler a static type: assign it to a typed variable, cast with `as String`, or apply the extension explicitly with `NumberParsing(d).parseInt()`. The explicit form requires the argument to be assignable to the on type, so a `dynamic` argument is accepted and checked at runtime.
  • In Dart, if a class later adds an instance method with the same name as an extension method, what happens to existing calls?
    They silently switch to the new instance method, because an instance member of the static type always takes precedence over extensions. That is a reason to avoid extension names likely to collide with future members of the extended type.

saying these in an interview costs you the question

  • Extension methods use the object's runtime type like virtual methods.
  • Calling an extension on dynamic is a compile-time error.
  • An extension method can override a method the class already has.
  • A subclass can override an extension member declared on its superclass.
  • Extension calls cost more than instance calls because of lookup tables.