Skip to main content

Null Safety & Optionals

Handling null/nil values is a common source of bugs. Here's how different languages approach null safety.

Nullable Types & Optionals​

// null and undefined are distinct
let a = null; // Intentionally empty
let b = undefined; // Not yet assigned
let c; // undefined

// Checking for null/undefined
if (value === null) { }
if (value === undefined) { }
if (value == null) { } // checks both null & undefined

// Nullish coalescing (??)
const name = user ?? "default";
const age = data.age ?? 0;

// Optional chaining (?.)
const city = user?.address?.city;
const first = arr?.[0];
const result = func?.();

// Nullish coalescing assignment
let x = null;
x ??= "default"; // x is now "default"

// Logical OR (includes falsy values)
const name2 = user || "default"; // "" -> "default"
const name3 = user ?? "default"; // "" -> ""

Key Takeaways​

  • Option/Optional types -- Rust (Option<T>), Swift (T?), Zig (?T), and Java (Optional<T>) provide explicit optional types instead of nullable references. In Rust, let x: Option<i32> = Some(42) or None -- there is no null; you use match, if let, or unwrap_or(0). Swift's var name: String? = nil and Zig's var value: ?i32 = null make optionality part of the type. Java's Optional<String> opt = Optional.ofNullable(name) wraps nullable values. These types force you to handle absence explicitly. Choose Option/Optional when you want compile-time enforcement; use nullable references (JavaScript, Python, C) when you prefer flexibility and runtime checks.

  • Null coalescing operators -- Most languages support ?? for default values: JavaScript's const name = user ?? "default", PHP's $name = $user ?? "default", C#'s string name = user ?? "default", Swift's let result = name ?? "default", and Zig's const result = value orelse 0. These ignore falsy values like "" or 0 (unlike || in JavaScript). Go has no ??; you use if user != nil { name = user.Name } else { name = "default" } or helper functions. Choose languages with ?? when you frequently need safe defaults; in Go, factor defaults into small helper functions.

  • Languages that eliminate null -- Rust has no null; optional values use Option<T>. Swift and Zig use optional types as the primary mechanism: in Swift, String? is distinct from String, and unwrapping with ! or if let is explicit. Zig's ?T forces orelse or if to handle null. This avoids the "billion-dollar mistake" of null dereferences. Python, JavaScript, Java, and C allow null/None/undefined anywhere, leading to NullPointerException or TypeError at runtime. If eliminating null bugs is a priority, choose Rust; Swift and Zig offer strong optional handling with less strictness than Rust.

  • Safe navigation -- Optional chaining (?.) in JavaScript, PHP, C#, Swift, and Ruby prevents null dereference errors when traversing nested structures. For example, user?.address?.city returns undefined or nil if any link is null instead of throwing. JavaScript's arr?.[0] and func?.() extend this to arrays and calls. Without ?., you write user && user.address && user.address.city (JavaScript) or nested if checks. Go has no ?.; you check if user != nil && user.Address != nil { city = user.Address.City }. Prefer ?. when working with deeply nested, loosely typed data (APIs, configs).

  • Null-coalescing assignment -- JavaScript (x ??= "default"), PHP ($name ??= "default"), and C# (name ??= "default") assign a default only when the variable is null or undefined. This keeps code concise when initializing optional fields. Go and Rust require explicit if or match. Use ??= when you have optional configuration or lazy defaults; in Go and Rust, use Option/if patterns for clarity.