Overview of Three Type Operators (as / is / is!)
| Operator | Function Description |
|---|---|
as | Forced type casting; also used to set library namespace aliases |
is | Type 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
asversion throws aCastErrorand crashes the program; - The
isversion skips the entireifblock 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
}