Syntax and Variables

Variables, sound null safety with ?, late and !, built-in types, and control flow in Dart.

Declaring variables

Dart
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.

Dart
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.

Dart
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:

Dart
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:

Dart
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 LateInitializationErrorlate 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:

Dart
String? nickname = 'Al';
String certain = nickname!;   // fine here — but crashes if nickname were actually null

Built-in types

Dart
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
Dart
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

Dart
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 the null case with ?., ??, or an explicit check — this just moves the crash from compile time to runtime.
  • Using const where a value actually needs to be computed at runtime — Dart will correctly reject it; use final instead.
  • Declaring a variable late without a clear guarantee it will be initialized before first use, risking a LateInitializationError at 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.