skip to content

Built-In Types

int, double and num, String with interpolation and UTF-16 code units, bool, and the special types Object, dynamic, void, Never and Null. Interviewers ask how dynamic differs from Object?.

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

explore

questions

6

In Dart, how does a variable typed dynamic differ from one typed Object?, and how do is, is! and as fit in?

level: juniorimportance: must knowfreq 70%

answer

  1. both accept every value
  2. one keeps static checking
  3. is promotes, as casts
  4. failed as throws TypeError
  5. Object excludes null

basics

~20 s

In Dart, Object? and dynamic both accept any value, including null, but Object? keeps static checking: you test with is or cast with as before using String members. dynamic switches checking off, so calls compile and fail only at runtime.

solid answer

~50 s

`Object` is the superclass of every class except `Null`, so `Object` means any non-null value and `Object?` means any value at all. On such a variable you can only call `Object` members like `toString()`; to use a `String` method you check `if (value is String)`, which promotes a local to `String`, or cast with `value as String`, which throws a `TypeError` if wrong. `is!` is the negated test. `dynamic` also accepts every value, but it **disables static checking**: any member access compiles and is checked at runtime, where a missing member throws `NoSuchMethodError`, and a `dynamic` value can be assigned to a `String` variable without a cast. Effective Dart says to use `Object?` or `Object` unless you really want dynamic dispatch; APIs such as decoded JSON (`Map<String, dynamic>`) are the usual exception, and even there you cast to precise types early.

code

dart · 26 lines
dart
void describe(Object? value) {
  if (value is String) {
    print('name with ${value.length} code units'); // promoted to String
  } else if (value is! num) {
    print('unsupported: ${value.runtimeType}');
  } else {
    print('price ${value * 2}'); // promoted to num
  }
}

void main() {
  describe('Margherita');
  describe(12);

  dynamic raw = 42;
  // raw.toUpperCase(); // compiles, but throws NoSuchMethodError
  try {
    final String name = raw; // implicit downcast compiles
    print(name);
  } on TypeError {
    print('raw was not a String');
  }
  Object boxed = 42;
  // final String bad = boxed; // compile error: needs a cast or a check
  print((boxed as int) + 1); // explicit cast
}

go deeper

for a junior

Recall that Object? accepts anything but keeps checking, while dynamic accepts anything and turns checking off.

for a middle

Explain implicit downcasts from dynamic, promotion after is and is!, and that a failed as throws TypeError.

for a senior

Contain dynamic at API boundaries such as JSON, cast early, and enable strict-casts and avoid_dynamic_calls in the project.

for a principal

Set a codebase policy on dynamic and analyzer strictness that balances migration effort against runtime type failures.

## Three ways to say "anything" Dart's type hierarchy has `Object` at the top of all classes, with one exception: `Null`, the class of `null`, is not a subtype of `Object`. Since sound null safety that gives three types that accept broad sets of values: | Type | Accepts | Static checking | Member access | |---|---|---|---| | `Object` | Every value except `null` | On | Only `Object` members (`toString`, `hashCode`, `==`, `runtimeType`) | | `Object?` | Every value, including `null` | On | Same, plus null checks | | `dynamic` | Every value, including `null` | **Off** | Anything compiles; checked at runtime | The null-safety guide calls `Object?` Dart's top type: if you need "any value", that is the type to write. ## What dynamic really changes `dynamic` is not a bigger `Object`; it is a request to **turn off static type checking** for that expression. dart.dev's built-in types page describes it exactly that way. Two consequences: 1. **Any member access compiles.** `value.toUpperCase()` compiles on a `dynamic` value; if the object has no such member at runtime, Dart throws `NoSuchMethodError`. 2. **Implicit downcasts are allowed.** `String name = value;` compiles when `value` is `dynamic`, and throws a `TypeError` at runtime if it holds, say, an `int`. The same assignment from an `Object` value is a compile-time error (`invalid_assignment`). ## is, is! and as With `Object?` you move from "anything" to a specific type explicitly: - **`is`** tests the runtime type and returns a `bool`. On a local variable, a successful test **promotes** it, so inside `if (value is String) { ... }` you can call `value.length` without a cast. - **`is!`** is the negation: `if (value is! String) return;` also promotes on the path that continues. - **`as`** casts: `value as String` returns the value typed as `String`, or throws a **`TypeError`** if it is not one. Dart 3 removed the old `CastError` class; `TypeError` is what a failed cast throws. Prefer `is` when the value might legitimately be another type, and `as` when any other type is a bug that should fail loudly. ## Menu importer example A menu importer reading a decoded JSON payload receives values typed `dynamic`. Writing `final String name = item['name'];` compiles, and a malformed payload with a number there crashes later with a `TypeError`. Treating the value as `Object?` and testing it keeps the failure where you can handle it: ```dart final Object? raw = item['name']; if (raw is! String) throw FormatException('name must be a string'); final name = raw; // promoted to String ``` ## Tooling that helps - The analyzer's `strict-casts` language mode reports implicit downcasts from `dynamic`, such as passing `jsonDecode(text)` straight to a `List<String>` parameter. - The `avoid_dynamic_calls` lint flags member access on `dynamic` targets. - Effective Dart's rule **"AVOID using `dynamic` unless you want to disable static checking"** states the policy; its companion rule says to annotate with `dynamic` explicitly rather than let inference fail silently. ## dynamic hiding inside generic types `dynamic` often arrives without anyone writing it: - A `Map<String, dynamic>` from decoded JSON makes every `map['key']` a `dynamic` expression. - A `List<dynamic>` makes every element access dynamic, so `items[0].price` compiles whatever the element is. - A declaration whose type inference fails, such as a variable with no initializer and no annotation, is silently `dynamic`. Each of these spreads unchecked code outward: a `dynamic` value assigned to an inferred local makes that local `dynamic` too. The defence is to convert at the boundary, reading `Object?` and checking with `is`, or casting once with `as` into a precise type, so the rest of the program is checked again. ## A short answer to give "`Object?` and `dynamic` both hold any value. `Object?` keeps the analyzer on, so I must prove the type with `is` or assert it with `as`. `dynamic` turns the analyzer off for that value, so mistakes compile and fail at runtime with `NoSuchMethodError` or `TypeError`. I use `Object?` by default and keep `dynamic` at API edges like JSON." ## Summary - Use `Object?` for "any value", `Object` for "any non-null value". - Use `dynamic` only when you deliberately want runtime dispatch or an API hands it to you. - Move from broad to specific types with `is` (safe test and promotion) or `as` (cast that throws `TypeError` on failure).

  • When is dynamic the right type in Dart?
    When you really want runtime dispatch, or when an existing API uses it, such as decoded JSON as `Map<String, dynamic>`. Effective Dart still advises casting those values to precise types early, and annotating `dynamic` explicitly rather than letting inference fall back to it silently.
  • How does the analyzer's strict-casts mode relate to dynamic?
    With `strict-casts: true` under `analyzer: language:` in analysis_options.yaml, the analyzer no longer allows implicit downcasts from `dynamic`, so passing a `dynamic` value where a `List<String>` is expected becomes an error until you add an explicit cast or check. It defaults to false.

saying these in an interview costs you the question

  • dynamic and Object are the same type with different names
  • A failed as cast returns null instead of throwing
  • Calling a missing method on a dynamic value is a compile error
  • Object accepts null, so Object? is redundant
  • Assigning an Object value to a String variable compiles without a cast
open as a page

In Dart, how are int, double and num related, and when does Dart convert between int and double for you?

level: juniorimportance: should knowfreq 52%

basics

~20 s

In Dart, int and double are both subtypes of num. An integer literal becomes a double where a double is expected, as in double z = 1, but a double never converts to int implicitly; you call toInt(), round() or truncate().

open as a page

In Dart, how do string interpolation, raw strings and multi-line strings work, and when is an interpolated string a compile-time constant?

level: juniorimportance: should knowfreq 48%

basics

~20 s

Dart interpolates $name or ${expression} into a string by calling toString(). A raw string (r'...') treats backslashes and $ literally, triple quotes create multi-line strings, and an interpolated string is const only if every interpolated value is a constant null, number, string or bool.

open as a page

In Dart, why can a menu item name containing emoji have a String.length larger than its visible character count, and how do you count characters correctly?

level: middleimportance: should knowfreq 40%

basics

~20 s

A Dart String is a sequence of UTF-16 code units, and length counts those units. Most emoji need two (a surrogate pair), and flags or family emoji combine several code points. Count user-perceived characters with the characters package, e.g. name.characters.length.

open as a page

In Dart's type system, what do void, Never and Null each mean, and where does each typically appear in code?

level: middleimportance: should knowfreq 33%

basics

~20 s

In Dart, void marks a value that must not be used, typically a return type; Never is the bottom type with no values, for expressions that cannot complete, such as throw; and Null is the type whose only value is null, a subtype of every nullable type.

open as a page

A Dart menu importer passes native tests, but compiled to JavaScript for the web it prints 20.0 as 20, sees 1.0 as an int and garbles large item IDs. Why?

level: seniorimportance: should knowfreq 28%

basics

~20 s

When Dart compiles to JavaScript, int and double share one 64-bit floating-point representation, so an int is a double with no fractional part. 1.0 is int is true, 20.0 prints as 20, and integers beyond 2^53 lose precision.

open as a page