Syntax and Variables
Variables, sound null safety with ?, late and !, built-in types, and control flow in Dart.
Declaring variables
var name = 'Ali'; // type inferred as String from the assigned value
String city = 'Lahore'; // explicit type annotation
final age = 22; // can only be assigned once, but computed at runtime
const pi = 3.14159; // must be a compile-time constant
final and const both prevent reassignment, but differ in when the value must be known: const requires a value fixed at compile time (a literal, or an expression made only of other const values), while final can be computed at runtime (e.g., from a function call or user input) but only assigned once.
final now = DateTime.now(); // fine — final, computed at runtime
const now = DateTime.now(); // compile error — DateTime.now() isn't a compile-time constant
Sound null safety
Since Dart 2.12, null safety is a core, non-optional part of the type system: every type is non-nullable by default, and you must explicitly opt in to allowing null with a ? suffix.
String name = 'Ali'; // non-nullable — can never be null
name = null; // compile error: null can't be assigned to a non-nullable variable
String? nickname; // nullable — can hold a String or null
nickname = 'Al'; // fine
nickname = null; // also fine
Because the compiler tracks nullability, it forces you to handle the null case before you can use a nullable value in a way that requires it to be present:
String? nickname;
print(nickname.length); // compile error: property 'length' can't be unconditionally accessed
if (nickname != null) {
print(nickname.length); // fine — Dart "promotes" nickname to String within this check
}
print(nickname?.length); // null-aware access — evaluates to null instead of throwing
print(nickname ?? 'Guest'); // '?? ' provides a fallback when the value is null
late — deferring initialization
late tells the compiler "trust me, this non-nullable variable will be initialized before it's ever read" — useful when a value can't be set immediately (e.g., in a constructor initializer list order, or a value computed lazily on first use) but you don't want to make it nullable:
class Config {
late String apiKey; // not set yet, but guaranteed non-null once it is
void loadFromEnvironment() {
apiKey = 'secret-key-123';
}
}
Reading a late variable before it's assigned throws a runtime LateInitializationError — late is a promise to the compiler, not a safety net, so use it only when you're confident about initialization order.
The ! operator (null assertion)
! force-casts a nullable value to its non-nullable counterpart, throwing at runtime if it's actually null — Dart's equivalent of Swift's force-unwrap, and it should be used just as sparingly:
String? nickname = 'Al';
String certain = nickname!; // fine here — but crashes if nickname were actually null
Built-in types
int age = 22;
double price = 9.99;
num anyNumber = 5; // can hold either an int or a double
bool isActive = true;
String message = 'Hello';
List<int> numbers = [1, 2, 3]; // ordered, growable collection
Set<String> tags = {'dart', 'flutter'}; // unordered, unique elements
Map<String, int> scores = {'Ali': 90, 'Sara': 85}; // key-value pairs
var numbers = [1, 2, 3];
numbers.add(4);
print(numbers); // [1, 2, 3, 4]
print(numbers.length); // 4
print(numbers[0]); // 1
var scores = {'Ali': 90, 'Sara': 85};
print(scores['Ali']); // 90
scores['Zara'] = 70;
Control flow
int score = 75;
if (score >= 90) {
print('A');
} else if (score >= 70) {
print('B');
} else {
print('C');
}
switch (score ~/ 10) { // ~/ is integer division
case 10:
case 9:
print('A');
break;
case 8:
case 7:
print('B');
break;
default:
print('C');
}
for (var i = 0; i < 3; i++) {
print(i);
}
for (final tag in ['dart', 'flutter']) {
print(tag);
}
var i = 0;
while (i < 3) {
print(i);
i++;
}
Common mistakes
- Reaching for
!reflexively to silence a null-safety compile error instead of actually handling thenullcase with?.,??, or an explicit check — this just moves the crash from compile time to runtime. - Using
constwhere a value actually needs to be computed at runtime — Dart will correctly reject it; usefinalinstead. - Declaring a variable
latewithout a clear guarantee it will be initialized before first use, risking aLateInitializationErrorat runtime.
Interview questions
Q: What is Dart's "sound" null safety, and what does it guarantee?
Every type is non-nullable by default; a type is only allowed to hold null if explicitly marked nullable with ?. "Sound" means the compiler can guarantee — not just suggest — that a non-nullable variable never holds null at runtime, which lets Dart safely skip null checks the compiler has already proven are unnecessary, both improving safety and performance.
Q: What's the difference between final and const?
Both prevent reassignment after the initial value is set. const requires the value to be known at compile time (and is deeply immutable — a const list's contents can't change either). final can be assigned a value computed at runtime (like the result of a function call), but still only once.