Hiprup

Explain sound null safety in Dart. How does it differ from Kotlin's null safety?

Dart sound null safety (since Dart 2.12 / Flutter 2.0): types are non-nullable by default; ? opts in to nullability; the compiler proves no null dereferences at compile time.

Key operators: ? nullable type, ! force-unwrap, ?. null-safe access, ?? null-coalesce, ??= assign-if-null, late deferred init, required for named params.

Dart vs Kotlin:

  • Both sound (compiler-proven safety).

  • Dart's late works on final fields; Kotlin's lateinit doesn't.

  • Dart has an explicit required keyword for named parameters; Kotlin uses non-null defaults + optional params.

  • Force-unwrap: ! in Dart, !! in Kotlin.

  • Null-coalesce: ?? in Dart, ?: 'Elvis' operator in Kotlin.

Opting out of null safety is no longer supported in modern Flutter.

Dart Null Safety (sound, since Dart 2.12 / Flutter 2.0):
  • Types are NON-NULLABLE by default
  • `?` makes a type nullable
  • Static analyzer + runtime guarantee no null dereference

int x = 1;        // non-nullable int — must always have a value
int? y;           // nullable int — can be null
String name = 'Alex';
// name = null;   // ❌ compile error

// 'late' = non-nullable but initialized later
late final User user;       // promise: I'll init before use
void init() => user = User.fetch();

// 'required' for named parameters
class Profile {
  Profile({required this.name, this.email});
  final String name;
  final String? email;
}

// Null-aware operators:
final len = name?.length;            // null-safe access
final value = name ?? 'default';     // null-coalescing
name ??= 'default';                  // assign-if-null
final first = list?.first ?? 0;      // chained

// 'sound' = the compiler PROVES no null dereferences
// (you can't lie to the type system with a runtime cast)

// =================================================
// Dart vs Kotlin null safety:
// =================================================
//
// Dart                              Kotlin
// ─────                              ──────
// String?     nullable               String?     nullable
// String      non-nullable            String      non-nullable
// !           force-unwrap            !!          force-unwrap
// ?.          null-safe access        ?.          null-safe access
// ??          null-coalesce           ?:          'Elvis' null-coalesce
// late        deferred init           lateinit    deferred init (different rules)
// required    keyword for params      no equivalent — Kotlin has
//                                     non-null default + nullable suffix
//
// Differences:
//   • Dart's `late` lets you defer initialization of FINAL variables.
//     Kotlin's `lateinit` only works on non-final, non-primitive vars.
//   • Dart distinguishes 'required' for NAMED parameters explicitly.
//   • Both are 'sound' — the type system PROVES safety at compile time.

The code progresses from basic non-nullable/nullable declarations to the four core null-aware operators (?., ??, ??=, !) and then to deferred initialization (late final) and required named parameters. The side-by-side table compares Dart's operators to Kotlin's so candidates coming from JVM mobile development can map equivalents quickly.

Interview tip: The key takeaway is that the static analyzer enforces these rules at compile time — runtime null dereferences become impossible without an explicit ! cast.

Two terms to use precisely: 'sound null safety' (the compiler proves no null dereferences — Dart 2.12+) and non-nullable by default. The Kotlin vs Dart comparison interviewers like: (1) late in Dart works on final fields, Kotlin's lateinit doesn't; (2) Dart has required for named parameters explicitly — Kotlin handles it via non-null defaults. End with the practical signal: 'in modern Flutter (since 2.0/Dart 2.12), opting out of null safety is no longer supported'.

Explain sound null safety in Dart. How does it differ from Kotlin's null safety? | Hiprup