An app assumes a JSON label is a string and calls an uppercase method. Test data works, but a numeric 404 causes a runtime error. Why would a similar mistake be caught earlier with var but remain hidden with dynamic?
The two declarations have different roles: var asks Dart to infer a static type, while dynamic relaxes static checking of operations on a value.
Does var mean the variable can change type?
With an initializer, Dart infers the variable's type. Here title is inferred as String. You can assign another string but not an integer:
var title = 'Ready'; // Inferred as String.
title = 'Sent'; // Allowed: still String.
// title = 404; // Analyzer error: int cannot be assigned to String.Uncommenting the last line produces an invalid_assignment diagnostic during static analysis. var does not mean “accept any type.” But var pending; without an initializer or another typing context has type dynamic; the spelling var alone does not guarantee a useful inferred type.
Why can a dynamic method call fail at runtime?
JSON arrives from outside the app. This example decodes one string label and one numeric label through the same path:
The diagram compares discovery times. An inferred String rejects assigning 404 during analysis; a dynamic value allows the call until an actual int lacks toUpperCase() at runtime.
import 'dart:convert';
void main() {
var title = 'Ready';
title = 'Sent';
print('var: ${title.toUpperCase()}');
for (final json in ['{"label":"Ready"}', '{"label":404}']) {
final payload = jsonDecode(json) as Map<String, dynamic>;
dynamic unchecked = payload['label'];
try {
print('dynamic: ${unchecked.toUpperCase()}');
} on NoSuchMethodError {
print('dynamic: method failed (${unchecked.runtimeType})');
}
Object? checked = payload['label'];
if (checked is String) {
print('checked: ${checked.toUpperCase()}');
} else {
print('checked: reject (${checked.runtimeType})');
}
}
}Run it with dart run main.dart:
var: SENT
dynamic: READY
checked: READY
dynamic: method failed (int)
checked: reject (int)The variable did not magically change an object's runtime type. The first JSON value is a String; the second is an int. Because unchecked is dynamic, the analyzer does not reject toUpperCase() in advance. The integer call fails only when that branch executes. The try/catch here shows the failure timing; it should not replace input validation in application code. Dart documents dynamic as disabling static checking.
How should you check an external JSON value?
Object? checked makes uncertainty explicit. Within if (checked is String), Dart can call string methods safely. A number can be handled as an unexpected input rather than as a mysterious missing-method exception.
| Declaration | What happens in this example | When the problem appears |
|---|---|---|
var title = 'Ready' | Type inferred as String; assigning 404 is rejected | Static analysis |
dynamic unchecked | Method call is allowed by static analysis; int lacks it | Runtime when invoked |
Object? checked plus is String | Checks type before calling a string method | Input validation |
If the UI label must be a string, decide whether a numeric 404 is a valid status code or a malformed API response before converting it to text. dynamic is sometimes necessary at an external boundary, but carrying it deep into app logic postpones errors.
Key takeaways
Initialized var infers a type; it does not permit every later assignment. dynamic skips useful static checks, so a wrong method call can fail only when executed. For external data, accept uncertainty at the boundary, test the type, and pass validated values into the rest of the app.

