Kotlin type system.

We introduced null safety concepts earlier. Now we see how Kotlin implements null safety through its type system. Today we learn the tools Kotlin provides to work with nullable types safely and effectively.

In Kotlin, types are non-nullable by default.

val name String equals "Alice". This cannot be null. Assigning null is a compile error.

To allow null, add a question mark: val name String question mark equals null.

String and String question mark are different types. String always has a value. String question mark might be null.

This distinction is the foundation of Kotlin's null safety. The type tells you whether null is possible.

You cannot assign String question mark to String without handling the null case. The compiler enforces this.

Think of question mark as asking: "is there a value here?" Sometimes yes, sometimes no.

Why does this matter?

In many languages, any reference can be null. You call a method, it crashes. NullPointerException. The billion-dollar mistake.

Kotlin moves null checks to compile time. If a type is non-nullable, the compiler guarantees it has a value. No runtime surprises.

If a type is nullable, you must handle the null case before using it. The compiler forces you to think about absence.

This does not eliminate null. It makes null explicit. You know where null can occur. You decide how to handle it.

The safe call operator calls a method only if the receiver is not null.

name question mark dot length. If name is not null, returns its length. If name is null, returns null.

No crash. No explicit if-check. The operator handles it.

Safe calls chain: user question mark dot address question mark dot city question mark dot uppercase. If any step is null, the whole expression is null.

The result type is nullable. name question mark dot length returns Int question mark, not Int.

Use safe calls when you want to proceed with null instead of crashing.

The Elvis operator provides a default when the left side is null.

name question mark colon "Unknown". If name is not null, use it. If null, use "Unknown".

Elvis is often combined with safe calls: name question mark dot length question mark colon 0. Get length if possible, otherwise 0.

Elvis can do more than provide defaults. It can throw: name question mark colon throw IllegalArgumentException.

Elvis can return early: val validName equals name question mark colon return.

The right side of Elvis executes only when needed. If the left side is not null, the right is skipped.

Sometimes you know a value is not null, even though the type is nullable.

The not-null assertion converts nullable to non-nullable: name double exclamation.

If name is not null, this returns the value as a non-nullable type.

If name is null, this throws NullPointerException.

Use double exclamation sparingly. It throws away null safety. If you are wrong, you crash.

Valid uses: after a check the compiler does not understand, in tests, when you are certain.

Prefer safe calls and Elvis. Use double exclamation only when you have no choice and are confident.

When you check a condition, Kotlin remembers.

if name not equals null, then inside the block, name is treated as non-nullable. No need for safe call.

This is a smart cast. The compiler sees the null check and narrows the type.

Smart casts work with type checks too: if obj is String, then inside the if, obj is String.

Smart casts work in when: when x, is String arrow x dot length, is Int arrow x plus 1.

For smart casts to work, the variable must be immutable or local. The compiler must be sure the value did not change.

Regular casts can fail: val s String equals obj as String. If obj is not a String, this crashes.

Safe casts return null on failure: val s String question mark equals obj as question mark String.

If obj is a String, s gets the value. If not, s is null. No crash.

Combine with Elvis: val s String equals obj as question mark String question mark colon "default".

Safe casts are useful when you do not know the type in advance.

The let function helps when you have a nullable value and want to work with it if present.

name question mark dot let with lambda, then inside, use the non-null value.

let takes a lambda. If name is not null, the lambda runs with the non-null value. If null, nothing happens.

Inside let, you work with a non-nullable value. No need for further null checks.

let is useful for side effects on nullable values. Send email only if address exists. Log only if message exists.

Let us summarize.

Nullable types use question mark. String is not String question mark.

Safe call question mark dot returns null if receiver is null.

Elvis question mark colon provides a default when null.

Not-null assertion double exclamation converts to non-nullable but can throw.

Smart casts narrow types after null checks.

Safe casts as question mark return null on failure.

let helps work with nullable values safely.

These tools make null handling explicit and safe.