Dart Type test operators

Overview of Three Type Operators (as / is / is!)

OperatorFunction Description
asForced type casting; also used to set library namespace aliases
isType check: returns true if the object matches the specified type
is!Inverted type check: returns true if the object does not match the specified type

The check obj is T evaluates to true when the object obj implements or inherits type T — this includes parent classes, interfaces, and nullable variants of the target type.

For example, anyObject is Object? will always return true.

The is Operator

Safe runtime type validation

It verifies an object’s type at runtime. If the check returns true, Dart automatically narrows the variable’s type inside the code block — no manual casting is required. If the object is null or the type does not match, the expression simply returns false with no runtime errors thrown.

The examples below use custom classes. If you haven’t learned about classes yet, you can think of them as blueprints for building structured data models.

class Person {
  String firstName = '';
}

class Employee extends Person {}

void main() {
  dynamic employee = Employee();

  // Use is to validate the object's type
  if (employee is Person) {
    // Inside this block, Dart treats employee as a Person automatically
    employee.firstName = "Jack";
    print(employee.firstName); // Output: Jack
  }

  dynamic emptyObj = null;
  if (emptyObj is Person) {
    // This block never runs; null does not match the Person type
    print("Type match succeeded");
  }
}Code language: Dart (dart)

This snippet defines a base Person class and a child Employee class that inherits from Person.

We use is to check if our object is a Person. Since employee uses the loose dynamic type annotation, Dart safely narrows its type to Person inside the if block once the check passes.

In the second example, emptyObj holds a null value, so emptyObj is Person resolves to false and the inner code block never executes.

The is! Operator

Inverted type check

Syntax:

variable is! Type

This operator is functionally identical to taking the negation of a standard is check.

It is equivalent to writing !(variable is Type), and returns true when the object is not an instance of the target type.

See the sample below:

class Person {
  String name = '';
}

void main() {
  dynamic obj = "sample text";
  if (obj is! Person) {
    print("obj is not a Person instance");
  }
}Code language: Dart (dart)

The code block here will execute and print the message, as obj is a string literal and not an object created from the Person class blueprint.

The as Operator

Forced explicit type casting

Usage syntax:

(variable as TargetType).propertyOrMethod()Code language: JavaScript (javascript)

Only use this operator when you are 100% certain the object matches your target type. If the cast fails, a runtime exception will be thrown. For more robust code, avoid overusing as.

class Teacher {
  String name = '';
}

void main() {
  dynamic jason = Teacher();
  // Cast to Teacher explicitly before accessing its property
  (jason as Teacher).name = "Jason";
  print((jason as Teacher).name); // Output: Jason
}

This example prints the string “Jason”.

We directly initialize jason as a Teacher instance stored in a dynamic variable, so we can safely cast it with as with no risk of runtime errors.

The code below will throw an error at runtime:

class Teacher {
  String name = '';
}

class Car {
  String name = '';
}

void main() {
  dynamic jason = Teacher();
  
  // Attempting to cast a Teacher instance to Car
  (jason as Car).name = "Jason"; // ❌ Runtime error
  print((jason as Car).name); // ❌ Runtime error
}

The error occurs because jason is a Teacher object, and we are trying to force-cast it to the unrelated Car type.

D:\dartdemo\firstdart>dart run
Building package executable...
Built firstdart:firstdart.
Unhandled exception:
type 'Teacher' is not a subtype of type 'Car' in type cast
#0      main (file:///D:/dartdemo/firstdart/bin/firstdart.dart:12:10)
#1      _delayEntrypointInvocation.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:314:19)
#2      _RawReceivePort._handleMessage (dart:isolate-patch/isolate_patch.dart:193:12)

Core Comparison: as vs is

// Approach 1: Forced cast with as
(employee as Person).firstName = 'Bob';

// Approach 2: Safe type check with is before usage
if (employee is Person) {
  employee.firstName = 'Bob';
}

When employee is null or not a Person instance:

  • The as version throws a CastError and crashes the program;
  • The is version skips the entire if block with zero errors or side effects.

For this reason, always prefer the is check pattern. It requires slightly more code but delivers far more stable, fault-tolerant applications.

Second Use Case for as

Beyond type casting, as can also assign custom aliases to imported library namespaces.

import 'dart:math' as math;

void main() {
  // Reference classes and methods via the library alias
  print(math.pi); // Output: 3.141592653589793
}

Full Combined Example

class Animal {}
class Cat extends Animal {}

void main() {
  dynamic obj = Cat();

  // 1. Basic type check with is
  if (obj is Animal) {
    print("obj is an Animal instance");
  }

  // 2. Inverted type check with is!
  if (obj is! String) {
    print("obj is not a string");
  }

  // 3. Explicit cast with as
  Animal a = obj as Animal;

  // 4. Broken cast example that crashes at runtime
  dynamic str = "hello";
  // Animal error = str as Animal; // Throws CastError
}

Leave a Reply

Your email address will not be published. Required fields are marked *