Type Annotations In Action

Type Annotations and Type Inference

A type annotation is an explicit bit of code that tells TypeScript what type of value some variable refers to.

In contrast, type inference tries to figure out the type for that variable for you.

The question for the developer is: when should you explicitly type a variable using type annotation, and when should you rely on type inference?

Type Annotations

Annotating variables

To type annotate a variable, just do this:

// Primitive types
const apples: number = 5;
const hasName: boolean = true;

Annotating objects

To annotate more complex object types, just do this:

// Class
const date: Date = new Date();
// Array
const colors: string[] = ['red', 'green', 'blue'];
// Object literal (for properties)
const point: { x: number; y: number } = { x: 10, y: 20 };

Annotating functions

To annotate a function, just do this:

const logNumber: (i: number) => void = (i: number) => {
  console.log(i);
};

Note: (i: number) => void is the syntax for type annotating a variable storing a function. (You'll learn how to annotate a function itself later.) The left-hand side tells TypeScript the types for the arguments, while the right-hand side tells TypeScript the type for the return value.

Understanding Type Inference

Take the following type annotation:

let apples: number = 5;

What happens if you remove the type annotation?

let apples = 5;

You will find that TypeScript still types apples as number. This is an example of type inference.

Note: Type inference only works if variable declaration and initialization happen on the same line. So, if I do it on a separate line, TypeScript won't automatically infer the type for us:

let apples;
apples = 5; // `apples` will be of type `any`

When to Use Type Annotations vs. Type Inference

In general, we want to try to use type inference. That's because it means less code to write.

However, there are at least 3 situations when you need to use type annotations:

  1. When a function returns the any type, and we need to clarify the value

  2. When you declare a variable on one line and delay initialization for another line

  3. When you want a variable to have a type that can't be inferred

Functions that returns the any type

The any type is a type that tells TypeScript that we have no idea what type the variable is.

Some functions that depend on the input to determine the return type will return the any type because it can't reasonably predict the return type.

For example, JSON.parse could return any number of types depending on the JSON string you input. If you pass "'false'", you get back a boolean. If you pass '5', you get back a number.

However, allowing JSON.parse to return the any type wastes the potential of TypeScript. If you don't type your variables, you can't get helpful error messages if you do something wrong.

// infers the `any` type
const age = JSON.parse('25');
// no error message for this fake property
age.notRealProperty;

To solve this, we explicitly include a type annotation:

const age: number = JSON.parse('25');
age.notRealProperty; // now TypeScript will complain

Delayed initialization

When you delay initialization, type inference doesn't work, and your variable is assigned the any type.

let target; // `any` type
if (isAvailable) {
  target = 'Ready to use';
} else {
  target = 'Not ready';
}

Similar to the function case above, this is bad practice in TypeScript.

To solve this problem, we just apply type annotation.

let target: string;
if (isAvailable) {
  target = 'Ready to use';
} else {
  target = 'Not ready';
}

When inference doesn't work

Type inference breaks down when the variable's type can be more than one type.

A real-world case of this might be you're storing a user's favouriteMedia, and that variable could receive either a Book instance, Movie instance, or Song instance.

To solve this, you can use the | operator, which is sort of like an or operator for your types.

Since type inference doesn't support multiple types, you must type annotate to use the | operator.

const media = [
  new Book({ rating: 90 }),
  new Movie({ rating: 98 }),
  new Song({ rating: 10 }),
];

let favouriteMedia: Book | Movie | Song;

media.forEach(item => {
  if (item.rating > 95) {
    favouriteMedia = item;
  }
});

Last updated