Values that may be missing
Think of a variable as a parcel locker. Most of the time there’s a parcel inside, but sometimes the locker is empty. If you reach in and try to open a parcel that isn’t there, things go wrong. In code, “empty” is called nil (Swift) or null (Kotlin and Java), and trying to use it crashes the app.
Real data is full of empty lockers: a user without a middle name, a product without a discount, an API response without an avatar URL. Here’s how that usually goes wrong. You show a user’s initials, and test it with your own name, Marko Petar Horvat:
let initials = user.firstName.prefix(1) + user.middleName.prefix(1)
// error: value of optional type 'String?' must be unwrappedval initials = user.firstName.take(1) + user.middleName.take(1)
// error: only safe (?.) or non-null asserted (!!.) calls are allowedString initials = user.firstName().substring(0, 1) + user.middleName().substring(0, 1);
// compiles, works for Marko, crashes for Anaconst initials = user.firstName.slice(0, 1) + user.middleName.slice(0, 1);
// error: 'user.middleName' is possibly 'null'Java
It works on your machine, passes code review and ships. Then Ana, who has no middle name, opens her profile: middleName() is null, and calling substring on null throws NullPointerException. The app closes in front of her.
Swift, Kotlin, TypeScript
The compiler stops you right there, because middleName may be missing. That error is the crash Ana would have hit after release (Ana has no middle name), caught before it ships.
You can’t avoid missing values, so the goal is to make sure you never forget to check.
That’s what null safetyNull safetyA language feature that makes the compiler check where a value might be missing, so you can't use it by accident.Open in glossary → does: the type of a variable tells you whether it can be empty, and the compiler refuses to let you use a maybe-empty value without handling the empty case first.
Tony Hoare called the null reference his “billion-dollar mistake”: in Java, every reference type silently includes null, so the type String really means “a String, or nothing”. Swift and Kotlin split that into two types, String and String?, and make the conversion from the second to the first explicit. The crash moves from runtime to a compile error you fix once.
The interesting parts for experienced developers are where that guarantee leaks: interop with Java and Objective-C, force unwraps, mutable properties that defeat smart casts, and Optional misuse in Java. They’re covered part by part below.
Declaring a value that may be missing
Add a ? to the type. In Java there’s no ?: any reference can be null, and the type doesn’t tell you. TypeScript (with strict on, as in every new project) spells it as a union: string | null.
struct User {
let firstName: String
let middleName: String? // may be nil
}
let ana = User(firstName: "Ana", middleName: nil)data class User(
val firstName: String,
val middleName: String?, // may be null
)
val ana = User(firstName = "Ana", middleName = null)// Any reference may be null; the type doesn't say which ones
record User(String firstName, String middleName) {}
var ana = new User("Ana", null);interface User {
firstName: string;
middleName: string | null; // may be null
}
const ana: User = { firstName: "Ana", middleName: null };TypeScript note
TypeScript has two empty values. null means “deliberately empty” (and it’s what JSON uses); undefined means “never set”, and it’s what you get from a missing property, a missing map key or ?.. An optional property, middleName?: string, is string | undefined. Checking value == null (two equals signs) catches both.
Java note
Java has two partial fixes: the Optional<T> wrapper for return values, and nullability annotations such as JSpecify’s @Nullable, which tools and Kotlin can check. Both appear below.
Notice that firstName has no ? (or | null). In Swift, Kotlin and TypeScript that’s a promise: it can never be empty, so you can use it anywhere without checking. Only middleName needs care.
Swift, Kotlin
String? is a different type from String, and getting from one to the other is always explicit: passing nil / null where a String is expected doesn’t compile.
Java
The type String silently includes null. The only signals are Optional<T> (for return values) and annotations like JSpecify’s @Nullable, which static analysis and Kotlin read but the JVM ignores.
TypeScript
With strictNullChecks (part of strict), string excludes null and undefined, and string | null must be narrowed before use. Without it, null fits every type, as in Java. Two empty values: null (deliberate, and what JSON uses) and undefined (missing property, missing key, the result of ?.); x == null matches both.
| Swift | Kotlin | Java | TypeScript | |
|---|---|---|---|---|
| Declare | String? | String? | @Nullable String, Optional<String> (returns) | string | null, name?: string |
Swift
Refresher:What String? really is
Swift’s String? is shorthand for Optional<String>, an enum with case none and case some(String). ?. is sugar for flatMap, and if let is pattern matching on .some.
Kotlin
Refresher:What String? really is
Kotlin’s String? is the same JVM reference as String; nullability exists only at compile time, plus a parameter check the compiler adds for Java callers. Int? is boxed as java.lang.Integer.
Java
Refresher:Optional vs @Nullable
Optional<String> is an ordinary wrapper object, one allocation per call. @Nullable is only an annotation: free at runtime, but only as good as the tools that check it.
Check this part
let a: Stringandlet b: String?. Which one can holdnil?val a: Stringandval b: String?. Which one can holdnull?String aandString b. Which one can holdnull?let a: stringandlet b: string | null. Which one can holdnull?How do you say that a value may be missing?Which Java tool says in a method's return type that it may find nothing?
How do you get from
String?toString?What does a Java variable of typeStringactually admit?How do you get fromstring | nulltostring?A function takes
name: String. Can a caller in your Swift code passnil?A function takesname: String. Can a caller in your Kotlin code passnull?BesidesOptionalfor return values, what tells readers and tools that a Java value may benull?A project hasstrict(and sostrictNullChecks) turned off. Can you passnullwhere astringis expected?