Mastering Dart Null Safety: Core Features, Practical Example, and Common Pitfalls
Learn how Dart’s null safety turns every type into a non‑nullable by default, how to use ‘?’, ‘late’, and ‘!’ safely, and how to verify the feature in your own project. The guide includes a concrete code example, configuration steps, and a checklist of typical mistakes to avoid.
21 Jul 2026, 16:27 UTC

Why Null Safety Matters
Null‑safety guarantees that a variable can never contain null unless you explicitly allow it. This eliminates a large class of runtime crashes caused by accidental null dereferences, and it gives the compiler and IDEs stronger guarantees about your code’s correctness.
Getting Started: Enabling Null Safety
Null safety is enabled automatically for new projects created with Dart 2.12 or later. If you are migrating an existing project, you must set the SDK constraint in pubspec.yaml:
environment:
sdk: '>=2.12.0 <3.0.0'
Run dart pub get and then dart analyze. The analyzer will report any violations of null‑safety rules. If you see errors like value of type 'String?' can't be assigned to a variable of type 'String', you need to add nullability annotations or refactor the code.
Core Features
- Non‑Nullable Types (default): Declaring
String name;meansnamecan never benull. The compiler forces an initializer or alatedeclaration. - Nullable Types: Append
?to the type:String? email;. The variable may benull, so you must check before dereferencing. - Late Initialization:
late String description;defers assignment until runtime. Accessing it before initialization throws aLateInitializationError. - Null Assertion Operator (
!): Treats a nullable value as non‑nullable after a runtime check. Use only when you’re certain the value isn’tnull.
Worked Example: Safe Navigation, Late, and Bang
Below is a small, self‑contained Dart file that demonstrates the four core features. Replace the placeholders with real logic in your project.
import 'dart:io';
// 1. Nullable type – user input may be missing
String? readUserName() {
print('Enter your name (or leave blank):');
final input = stdin.readLineSync();
return input?.isEmpty ?? true ? null : input;
}
// 2. Late initialization – value set after construction
class Profile {
late final String bio; // will be set in initBio
void initBio(String text) {
bio = text;
}
}
// 3. Null assertion – we’ll assert after a check
String getGreeting(String? name) {
if (name == null) {
throw ArgumentError('Name cannot be null');
}
return 'Hello, ${name!}'; // safe after the null check
}
void main() {
// Safe navigation: may print null if name is missing
final name = readUserName();
print('Name length: ${name?.length}');
// Late usage
final profile = Profile();
profile.initBio('Loves Flutter and Dart');
print('Bio: ${profile.bio}');
// Bang operator usage
final greeting = getGreeting(name);
print(greeting);
}
Running this file will:
- Prompt for a name; if you press Enter,
namebecomesnull. - Print the name length or
null. - Initialize
profile.biousinglate. - Generate a greeting using the bang operator after a null check.
Common Pitfalls & Limitations
- Unconditional Bang: Using
value!without a preceding null check causes aNullThrownErrorat runtime. Always guard withif (value != null)orassert(value != null). - Late Initialization Errors: Accessing a
latevariable before assignment throwsLateInitializationError. This can be hard to trace; consider using a constructor or factory to guarantee initialization. - Migration Overhead: Legacy code without nullability annotations will produce many compile‑time errors. A staged migration—enabling null safety for a subset of files and gradually adding annotations—is recommended.
- Interoperability with C/C++ or FFI: Values coming from native code are treated as nullable; you must handle them appropriately.
- Platform Differences: The analyzer enforces rules at compile time, but runtime behavior depends on the platform (e.g., web vs. native). Always run
dart runorflutter runto catch runtime null errors.
Verifying Null Safety in Your Project
- Ensure your SDK constraint is at least 2.12 in
pubspec.yaml. - Run
dart analyze. The analyzer should report no null‑safety errors if the code is compliant. - Create a minimal test file that declares a nullable variable, assigns
null, and uses safe navigation (?.). Running it should produce no crash and printnullor a value. - Try accessing a
latevariable before initialization to confirm that aLateInitializationErroris thrown. - Use
dart --enable-asserts runto ensure thatassertstatements are active in production builds if you rely on them for null checks.
These steps confirm that null safety is active and that your code respects the compiler’s guarantees.
By following the patterns above—explicit nullability, safe navigation, cautious use of late, and guarded bang assertions—you’ll write Dart code that is both safer and more maintainable.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.