TypeScript advanced types with examples

In the previous post, I covered the frequently used TypeScript basic types. In this post, I’ll shed some light on some of the advanced types that enable us to clearly define our intention when things go beyond the basics.

You can try the below examples in the TypeScript playground.

Union types

We know that we can tell TypeScript to set the type of a variable to be of a specific type. How can we specify the type to be of multiple types i.e. either a number or string? Surely we can use any, but then TypeScript will not complain if other types are passed. Let’s see that below in probably the most dumbest example ever!

// function that returns the argument. That's it!
function dumbest(value: any): any {
  return value;
}

dumbest(123); // it works
dumbest('123'); // it works too
dumbest(true); // no problem!

For those wondering about the colon after the function argument, this is the way to set the type of arguments. There’s another colon notation after that and that is for declaring function return type.

It’s evident that the behavior is inconsistent with what we want and that is TypeScript should yell if it is not a number or string. To specify our intention we’ll use union types.

function dumbest(value: number | string): number | string {
  return value;
}

dumbest(123); // it works
dumbest('123'); // it works too

/*
  The following will throw error during compilation
*/
dumbest(true); // Argument of type 'true' is not assignable to parameter of type 'string | number'.

Nullable types

By default, TypeScript will do a “great favor” for us by assigning null and undefined to anything whenever possible. To change this behavior we have to tell the compiler to not to follow default behavior. Setting --strictNullChecks flag in the tsconfig.json will make the null and undefined values assignable only through explicit declaration. Did I tell you that null and undefined are TypeScript’s two special types and hold the values null and undefined respectively?

Union types become handy when assigning null or undefined to a variable.

// When --strictNullChecks is enabled

let aString: string;

aString = 'ten'; // it works as expected
aString = null; // Type 'null' is not assignable to type 'string' :(

// Let's use union types
let aNumber: number | null;

aNumber = 10; // it works
aNumber = null; // no problem!

TypeScript automatically adds | undefined to the optional function parameter and optional class properties. Let’s see this in action below.

/*
  Adding "?" after the parameter makes it optional.

  Since the parameter type can be "undefined", the "| undefined" need to be added
  to the return type too. Otherwise the following error will be thrown-

  "Type 'number | undefined' is not assignable to type 'number'.
  Type 'undefined' is not assignable to type 'number'."
*/
function dumbest(value?: number): number | undefined {
  return value;
}

Type Aliases

Type Aliases make it possible to give a name for the type. It is very useful when describing the shape of the data.

For example, we’re hitting an API to get the data and do something with it. The API returns lots of data but we want only what we care about. Here we’ll use the GitHub API, and fetch public events. The fetched data looks like below.

[
  {
    id: '11448873308',
    type: 'PushEvent',
    actor: {
      id: 32588984,
      login: 'pra8eek',
      display_login: 'pra8eek',
      gravatar_id: '',
      url: 'https://api.github.com/users/pra8eek',
      avatar_url: 'https://avatars.githubusercontent.com/u/32588984?',
    },
    repo: {
      id: 216354720,
      name: 'pra8eek/OneChat',
      url: 'https://api.github.com/repos/pra8eek/OneChat',
    },
    payload: {
      push_id: 4593875342,
      size: 1,
      distinct_size: 1,
      ref: 'refs/heads/master',
      head: 'dd56e9dc5f4fa9adaf6329b89261036fd0182077',
      before: 'ecef2e603777c11b9df22445bacc42bb6fa0324b',
      commits: [
        {
          sha: 'dd56e9dc5f4fa9adaf6329b89261036fd0182077',
          author: {
            email: '32588984+pra8eek@users.noreply.github.com',
            name: 'Prateek Bhardwaj',
          },
          message: 'Add files via upload',
          distinct: true,
          url:
            'https://api.github.com/repos/pra8eek/OneChat/commits/dd56e9dc5f4fa9adaf6329b89261036fd0182077',
        },
      ],
    },
    public: true,
    created_at: '2020-02-06T05:11:44Z',
  },
  ...
  ...
];

Let’s write a function that makes the API call and returns the data we want from it.

// declare the shape for the specific data we care about from the API
type FetchedEventType = {
  id: number;
  type: string;
  actor: {
    id: string;
    url: string;
  };
  repo: {
    id: string;
  };
};

// declare types for the returned event type from the function
type OutputEventType = {
  eventId: number;
  eventType: string;
  actorId: number;
  actorURL: string;
  repoId: number;
};

async function getEventsData(): Promise<OutputEventType[]> {
  // It's okay here as documenting the exact shape is cumbersome.
  const events: any = await fetch('https://api.github.com/events');

  return events.map(event: FetchedEventType => ({
    eventId: Number(event.id),
    eventType: event.type,
    actorId: event.actor.id,
    actorURL: event.actor.url,
    repoId: event.repo.id,
  }));
}

In the above example, at first, we specify the shape of the data using the type keyword. Then we set the function return type to be a promise of an array of OutputEventType. We’re iterating over the events and telling TypeScript that each event is a type of FetchedEventType. This helps to understand the shape of the data at a glance.

There are more advanced types I haven’t covered in this post purposefully. We should be careful using TypeScript types as it can go complex which may hurt code readability. I would suggest to keep it simple in the professional environment so that other members can jump right into the business logic rather than trying to uncover the typing logic.

Share on

Get the latest post in your inbox!

Kamal Sharif
Hi, I'm Md. Kamal Sharif. I'm a senior frontend developer based in Dhaka, Bangladesh.

Follow Blog

Posts you might like

Git Rebase - how to squash multiple commits into a single commitWhy I decided to become a software engineer at the age of 26Advance usage of Array.prototype.reduce() in JavaScript