TypeScript
  • Introduction
  • Introduction
    • What is TypeScript?
    • Why TypeScript?
    • Setup
  • Beginner
    • Basic types
      • Primitive types
      • Arrays and tuples
      • Enums
      • Any type
    • Objects
    • Type aliases
    • Interfaces
    • Functions
      • Function signatures
      • Void type
      • Functions as types
    • Union types
    • Type guards
    • Intersection types
    • Nullable types
    • Inference
  • Resources
    • Resources
Powered by GitBook
On this page
  • Explicit nullability
  • Optional properties
  1. Beginner

Nullable types

Tony Hoare, the inventor of null, reflects on his mistake:

I call it my billion-dollar mistake. It was the invention of the null reference in 1965. At that time, I was designing the first comprehensive type system for references in an object oriented language (ALGOL W). My goal was to ensure that all use of references should be absolutely safe, with checking performed automatically by the compiler. But I couldn't resist the temptation to put in a null reference, simply because it was so easy to implement. This has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years. In recent years, a number of program analysers like PREfix and PREfast in Microsoft have been used to check references, and give warnings if there is a risk they may be non-null. More recent programming languages like Spec# have introduced declarations for non-null references. This is the solution, which I rejected in 1965.

Prior to TypeScript 2.0, all types could be legally assigned null or undefined values.

For example:

let dogName: string = 'Noodle';
dogName = null;      // OK
dogName = undefined; // OK

Put another way, null and undefined were subtypes of every type, from primitive types to objects and functions.

Then the strictNullChecks flag was introduced in TypeScript 2.0, which is a a compiler option specified in tsconfig.json:

{
  "compilerOptions": {
    "strictNullChecks": true
  }
}

With strictNullChecks turned on, we can no longer assign null or undefined to our variable:

// Compiled with --strictNullChecks

let dogName: string = 'Noodle';
dogName = null;      // Error: Type 'null' is not assignable to type 'string'.
dogName = undefined; // Error: Type 'undefined' is not assignable to type 'string'.

Explicit nullability

Since types are non-nullable when strict null checking is enabled, we'll need to explicitly opt in for certain types to be nullable. We do this by simply creating a union type that includes null and/or undefined:

// Compiled with --strictNullChecks

let dogName: string | null = 'Noodle';
dogName = null;      // OK
dogName = undefined; // Error: Type 'undefined' is not assignable to type 'string | null'.

Optional properties

If our types are non-nullable, then how are optional properties allowed? Let's see how TypeScript handles this:

interface Dog {
  name: string;
  siblings?: Dog[];
}

const dog: Dog = {
  name: 'Noodle'
}

dog.siblings = null; // Error: Type 'null' is not assignable to type 'Dog[] | undefined'.

Here we see that TypeScript automatically creates a union type that includes undefined with whatever optional properties we specified.

Now, consider a function like countSiblings:

interface Dog {
  name: string;
  siblings?: Dog[];
}

const dog: Dog = {
  name: 'Noodle'
}

function countSiblings(dogs: Dog[]): number {
  return dogs.length;
}

countSiblings(dog.siblings) // Error: Argument of type 'Dog[] | undefined' is not assignable to parameter of type 'Dog[]'.

if (typeof dog.siblings !== 'undefined') {
  countSiblings(dog.siblings); // OK
}

if (dog.siblings) {
  countSiblings(dog.siblings); // OK
}

Recall that siblings is an optional property. The type checker, therefore, cannot guarantee it will be defined. Hence our need to guard against the possible undefined case.

PreviousIntersection typesNextInference

Last updated 6 years ago