DEV Community

Cover image for Advanced TypeScript Types cheat sheet (with examples)
Ibrahima Ndaw
Ibrahima Ndaw

Posted on • Originally published at ibrahima-ndaw.com

Advanced TypeScript Types cheat sheet (with examples)

TypeScript is a typed language that allows you to specify the type of variables, function parameters, returned values, and object properties.

Here an advanced TypeScript Types cheat sheet with examples.

Let's dive in

Originally posted on my blog

Intersection Types

An intersection type is a way of combining multiple types into one. Meaning that you can merge a given type A with a type B or more and get back a single type with all properties.

type LeftType = {
  id: number
  left: string
}

type RightType = {
  id: number
  right: string
}

type IntersectionType = LeftType & RightType

function showType(args: IntersectionType) {
  console.log(args)
}

showType({ id: 1, left: "test", right: "test" })
// Output: {id: 1, left: "test", right: "test"}
Enter fullscreen mode Exit fullscreen mode

As you can see, IntersectionType combines two types - LeftType and RightType and use the & sign to construct the intersection type.

Union Types

Union types allow you to have different types annotation within a given variable.

type UnionType = string | number

function showType(arg: UnionType) {
  console.log(arg)
}

showType("test")
// Output: test

showType(7)
// Output: 7
Enter fullscreen mode Exit fullscreen mode

The function showType is a union type that accepts both strings and numbers as a parameter.

Generic Types

A generic type is a way of reusing part of a given type. It helps to capture the type T passed in as a parameter.

function showType<T>(args: T) {
  console.log(args)
}

showType("test")
// Output: "test"

showType(1)
// Output: 1
Enter fullscreen mode Exit fullscreen mode

To construct a generic type, you need to use the brackets and pass T as a parameter.
Here, I use T (the name is up to you) and then, call the function showType twice with different type annotations because it's generic - it can be reused.

interface GenericType<T> {
  id: number
  name: T
}

function showType(args: GenericType<string>) {
  console.log(args)
}

showType({ id: 1, name: "test" })
// Output: {id: 1, name: "test"}

function showTypeTwo(args: GenericType<number>) {
  console.log(args)
}

showTypeTwo({ id: 1, name: 4 })
// Output: {id: 1, name: 4}
Enter fullscreen mode Exit fullscreen mode

Here, we have another example that has an interface GenericType which receives a generic type T. And since it's reusable, we can call it with first a string, and then a number.

interface GenericType<T, U> {
  id: T
  name: U
}

function showType(args: GenericType<number, string>) {
  console.log(args)
}

showType({ id: 1, name: "test" })
// Output: {id: 1, name: "test"}

function showTypeTwo(args: GenericType<string, string[]>) {
  console.log(args)
}

showTypeTwo({ id: "001", name: ["This", "is", "a", "Test"] })
// Output: {id: "001", name: Array["This", "is", "a", "Test"]}
Enter fullscreen mode Exit fullscreen mode

A generic type can receive several arguments. Here, we pass in two parameters: T and U, and then use them as type annotations for the properties. That said, we can now use the interface and provide different types as argument.

Utility Types

TypeScript provides handy built-in utilities that help to manipulate types easily. To use them, you need to pass into the <> the type you want to transform.

Partial

  • Partial<T>

Partial allows you to make all properties of the type T optional. It will add a ? mark next to every field.

interface PartialType {
  id: number
  firstName: string
  lastName: string
}

function showType(args: Partial<PartialType>) {
  console.log(args)
}

showType({ id: 1 })
// Output: {id: 1}

showType({ firstName: "John", lastName: "Doe" })
// Output: {firstName: "John", lastName: "Doe"}
Enter fullscreen mode Exit fullscreen mode

As you can see, we have an interface PartialType which is used as type annotation for the parameters received by the function showType(). And to make the properties optional, we have to use the Partial keyword and pass in the type PartialType as an argument. That said, now all fields become optional.

Required

  • Required<T>

Unlike Partial, the Required utility makes all properties of the type T required.

interface RequiredType {
  id: number
  firstName?: string
  lastName?: string
}

function showType(args: Required<RequiredType>) {
  console.log(args)
}

showType({ id: 1, firstName: "John", lastName: "Doe" })
// Output: { id: 1, firstName: "John", lastName: "Doe" }

showType({ id: 1 })
// Error: Type '{ id: number: }' is missing the following properties from type 'Required<RequiredType>': firstName, lastName
Enter fullscreen mode Exit fullscreen mode

The Required utility will make all properties required even if we make them optional first before using the utility. And if a property is omitted, TypeScript will throw an error.

Readonly

  • Readonly<T>

This utility type will transform all properties of the type T in order to make them not reassignable with a new value.

interface ReadonlyType {
  id: number
  name: string
}

function showType(args: Readonly<ReadonlyType>) {
  args.id = 4
  console.log(args)
}

showType({ id: 1, name: "Doe" })
// Error: Cannot assign to 'id' because it is a read-only property.
Enter fullscreen mode Exit fullscreen mode

Here, we use the utility Readonly to make the properties of ReadonlyType not reassignable. That said, if you try to give a new value to one of these fields, an error will be thrown.

Besides that, you can also use the keyword readonly in front of a property to make it not reassignable.

interface ReadonlyType {
  readonly id: number
  name: string
}
Enter fullscreen mode Exit fullscreen mode

Pick

  • Pick<T, K>

It allows you to create a new type from an existing model T by selecting some properties K of that type.

interface PickType {
  id: number
  firstName: string
  lastName: string
}

function showType(args: Pick<PickType, "firstName" | "lastName">) {
  console.log(args)
}

showType({ firstName: "John", lastName: "Doe" })
// Output: {firstName: "John"}

showType({ id: 3 })
// Error: Object literal may only specify known properties, and 'id' does not exist in type 'Pick<PickType, "firstName" | "lastName">'
Enter fullscreen mode Exit fullscreen mode

Pick is a bit different from the previous utilities we have already seen. It expects two parameters - T is the type you want to pick elements from and K which is the property you want to select. You can also pick multiple fields by separating them with a pipe(|) symbol.

Omit

  • Omit<T, K>

The Omit utility is the opposite of the Pick type. And instead of selecting elements, it will remove K properties from the type T.

interface PickType {
  id: number
  firstName: string
  lastName: string
}

function showType(args: Omit<PickType, "firstName" | "lastName">) {
  console.log(args)
}

showType({ id: 7 })
// Output: {id: 7}

showType({ firstName: "John" })
// Error: Object literal may only specify known properties, and 'firstName' does not exist in type 'Pick<PickType, "id">'
Enter fullscreen mode Exit fullscreen mode

This utility is similar to the way Pick works. It expects the type and the properties to omit from that type.

Extract

  • Extract<T, U>

Extract allows you to construct a type by picking properties that are present in two different types. The utility will extract from T all properties that are assignable to U.

interface FirstType {
  id: number
  firstName: string
  lastName: string
}

interface SecondType {
  id: number
  address: string
  city: string
}

type ExtractType = Extract<keyof FirstType, keyof SecondType>
// Output: "id"
Enter fullscreen mode Exit fullscreen mode

Here, we have two types that have in common the property id. And hence by using the Extract keyword, we get back the field id since it's present in both interfaces. And if you have more than one shared field, the utility will extract all similar properties.

Exclude

Unlike Extract, the Exclude utility will construct a type by excluding properties that are already present in two different types. It excludes from T all fields that are assignable to U.

interface FirstType {
  id: number
  firstName: string
  lastName: string
}

interface SecondType {
  id: number
  address: string
  city: string
}

type ExcludeType = Exclude<keyof FirstType, keyof SecondType>

// Output; "firstName" | "lastName"
Enter fullscreen mode Exit fullscreen mode

As you can see here, the properties firstName and lastName are assignable to the SecondType type since they are not present there. And by using the Extract keyword, we get back these fields as expected.

Record

  • Record<K,T>

This utility helps you to construct a type with a set of properties K of a given type T. Record is really handy when it comes to mapping the properties of a type to another one.

interface EmployeeType {
  id: number
  fullname: string
  role: string
}

let employees: Record<number, EmployeeType> = {
  0: { id: 1, fullname: "John Doe", role: "Designer" },
  1: { id: 2, fullname: "Ibrahima Fall", role: "Developer" },
  2: { id: 3, fullname: "Sara Duckson", role: "Developer" },
}

// 0: { id: 1, fullname: "John Doe", role: "Designer" },
// 1: { id: 2, fullname: "Ibrahima Fall", role: "Developer" },
// 2: { id: 3, fullname: "Sara Duckson", role: "Developer" }
Enter fullscreen mode Exit fullscreen mode

The way Record works is relatively simple. Here, it expects a number as a type which is why we have 0, 1, and 2 as keys for the employees variable. And if you try to use a string as a property, an error will be thrown. Next, the set of properties is given by EmployeeType hence the object with the fields id, fullName, and role.

NonNullable

  • NonNullable<T>

It allows you to remove null and undefined from the type T.

type NonNullableType = string | number | null | undefined

function showType(args: NonNullable<NonNullableType>) {
  console.log(args)
}

showType("test")
// Output: "test"

showType(1)
// Output: 1

showType(null)
// Error: Argument of type 'null' is not assignable to parameter of type 'string | number'.

showType(undefined)
// Error: Argument of type 'undefined' is not assignable to parameter of type 'string | number'.
Enter fullscreen mode Exit fullscreen mode

Here, we pass the type NonNullableType as an argument to the NonNullable utility which constructs a new type by excluding null and undefined from that type. That said, if you pass a nullable value, TypeScript will throw an error.

By the way, if you add the --strictNullChecks flag to the tsconfig file, TypeScript will apply non-nullability rules.

Mapped types

Mapped types allow you to take an existing model and transform each of its properties into a new type. Note that some utility types covered earlier are also mapped types.

type StringMap<T> = {
  [P in keyof T]: string
}

function showType(arg: StringMap<{ id: number; name: string }>) {
  console.log(arg)
}

showType({ id: 1, name: "Test" })
// Error: Type 'number' is not assignable to type 'string'.

showType({ id: "testId", name: "This is a Test" })
// Output: {id: "testId", name: "This is a Test"}
Enter fullscreen mode Exit fullscreen mode

StringMap<> will transform whatever types that passed in into a string. That said, if we use it in the function showType(), the parameters received must be a string - otherwise, an error will be thrown by TypeScript.

Type Guards

Type Guards allow you to check the type of a variable or an object with an operator. It's a conditional block that returns a type using typeof, instanceof, or in.

  • typeof
function showType(x: number | string) {
  if (typeof x === "number") {
    return `The result is ${x + x}`
  }
  throw new Error(`This operation can't be done on a ${typeof x}`)
}

showType("I'm not a number")
// Error: This operation can't be done on a string

showType(7)
// Output: The result is 14
Enter fullscreen mode Exit fullscreen mode

As you can see, we have a normal JavaScript conditional block that checks the type of the argument received with typeof. With that in place, you can now guard your type with this condition.

  • instanceof
class Foo {
  bar() {
    return "Hello World"
  }
}

class Bar {
  baz = "123"
}

function showType(arg: Foo | Bar) {
  if (arg instanceof Foo) {
    console.log(arg.bar())
    return arg.bar()
  }

  throw new Error("The type is not supported")
}

showType(new Foo())
// Output: Hello World

showType(new Bar())
// Error: The type is not supported
Enter fullscreen mode Exit fullscreen mode

Like the previous example, this one is also a type guard that checks if the parameter received is part of the Foo class or not and handles it consequently.

  • in
interface FirstType {
  x: number
}
interface SecondType {
  y: string
}

function showType(arg: FirstType | SecondType) {
  if ("x" in arg) {
    console.log(`The property ${arg.x} exists`)
    return `The property ${arg.x} exists`
  }
  throw new Error("This type is not expected")
}

showType({ x: 7 })
// Output: The property 7 exists

showType({ y: "ccc" })
// Error: This type is not expected
Enter fullscreen mode Exit fullscreen mode

The in operator allows you to check whether a property x exists or not on the object received as a parameter.

Conditional Types

It tests two types and selects one of them depending on the outcome of that test.

type NonNullable<T> = T extends null | undefined ? never : T
Enter fullscreen mode Exit fullscreen mode

This example of the NonNullable utility type checks if the type is null or not and handle it depending on that. And as you can note, it uses the JavaScript ternary operator.

Thanks for reading.

You can find other great content like this on my blog or follow me on Twitter to get notified.

Top comments (41)

Collapse
 
khrisl33t profile image
kHRISl33t

A really well-written article with good examples. Good job!

Would like to add one more thing I like to use and you might find it interesting:

type Maybe<T> = T | null;

function foo(bar: Maybe<string>) {
  if (!bar) {
    // handle if bar is null
  }
  // do stuff if value not null.
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
ionellupu profile image
Ionel Cristian Lupu

What are some really good real world examples for this? (3,4 examples).

Wouldn't the optional operator be enough?

function foo(bar?: string) {
  if (!bar) {
    // handle if bar is falsy
  }
  // do stuff if value not falsy.
}
Enter fullscreen mode Exit fullscreen mode

The only thing I can think of is when you really need the parameter to be exactly null. But I never have this use case in the projects I work on, so I think something is fishy :))

Collapse
 
michaeljota profile image
Michael De Abreu

The difference between the two, is that in Maybe the value is required, and in the second one is optional. So, you can have Maybe values and those values would be either defined or not, but would still be required. I have found this to be useful in React.

Collapse
 
khrisl33t profile image
kHRISl33t

Actually it's just personal preference to have null values instead of undefined.
The example I provided is actually not that great, because I tend to use this more with objects and their properties.

Collapse
 
chico1992 profile image
chico1992

You should explicitly check for bar===null as in your case the empty string will also make !bar true

Collapse
 
khrisl33t profile image
kHRISl33t

you are right, thanks :)

Collapse
 
cesarkohl profile image
Cesar Kohl

Great article! Please remember to use semicolons at the end of the lines.

Collapse
 
ibrahima92 profile image
Ibrahima Ndaw

The semicolon is not mandatory, I prefer to not use it. Thanks for reading BTW

Collapse
 
garretharp profile image
Garret

No semicolon makes it look so much cleaner:)

Collapse
 
randalvance profile image
Randal Vance Cunanan

Use prettier, you don't even have to type it yourself.

Collapse
 
cesarkohl profile image
Cesar Kohl

It's not mandatory if you wanna go against Google: google.github.io/styleguide/jsguid... . Please reconsider.

Thread Thread
 
garretharp profile image
Garret

Theres a million different style guides, use whatever you want. No one is forced to use a specific one.

Collapse
 
psiho profile image
Mirko Vukušić • Edited

:) so glad I don't have to see or type those anymore

Collapse
 
mutterpedro profile image
Pedro Mutter

Congratulations! Really good article, very useful! I would like to add the possibility to create your own type guards on the type guard section, here is an example:

typeguard example

Collapse
 
ibrahima92 profile image
Ibrahima Ndaw

Great! A really good example, I will add it to the article.

Collapse
 
skye profile image
Skye Wu

i think : obj is ImportantType is ok, but can be more simpler:

function isImportantType(obj: any): boolean {
  return obj.id && obj.requiredFiled;
}
Collapse
 
toddmath profile image
Todd Matheson

That defeats the purpose of using the type guard altogether. Typescript won’t infer the type from that function. :obj is ImportantType is the what tells typescript what the passed parameters type is. Hence type guard.

Thread Thread
 
skye profile image
Skye Wu

thanks for explain

Collapse
 
mutterpedro profile image
Pedro Mutter

But it wouldn't be a type guard though.

Thread Thread
 
skye profile image
Skye Wu

thanks for explain

Collapse
 
marcuzy profile image
Oleg Krasavin

Seems typescript is getting overcomplicated.

Collapse
 
luisaceituno profile image
Luis Aceituno

How so? These have been in it for a long time now. You are also not required to use them, since they're mostly syntactic sugar. It does make life easier a lot of times though!

Collapse
 
ibrahima92 profile image
Ibrahima Ndaw

Absolutely.

Collapse
 
bbehzadi profile image
bbehzadi

Good article, to the point and concise with good examples.

I wish I knew Partial<T> - that I just learnt here before. This is how I implemented it:

export type ObjectWithOptionalProps<T> = { [key in keyof T]?: T[key] };
Collapse
 
yurikaradzhov profile image
Yuri Karadzhov

Good article, nice to have everything combined in one place.

Collapse
 
ibrahima92 profile image
Ibrahima Ndaw

Thanks for reading too.

Collapse
 
tsimbalar profile image
Thibaud Desodt

Really concise summary / recap.

Will share that with my team, thanks a lot !

Collapse
 
ibrahima92 profile image
Ibrahima Ndaw

Great! I'm glad this article could help.

Collapse
 
welingtonveiga profile image
Welington Veiga • Edited

It is a list of "easy to get" examples of the most interesting TS typing features, it's quite handy having them packed like this, thank you!

Collapse
 
ibrahima92 profile image
Ibrahima Ndaw

Great! I'm glad you find value in this article.

Collapse
 
evgenyartemov profile image
Evgeny Artemov

OMG, what I see! The first well wrote and detailed explanation of the typescript types system!!! Thanks a lot!!!

Collapse
 
ibrahima92 profile image
Ibrahima Ndaw

Awesome! I'm glad you find value on it.

Collapse
 
richardburd profile image
Richard Burd

Thank you for writing this article Ibrahima! I have been looking to update my beginner's TypeScript cheat sheet with utility types and this is the best explanation I've seen so far!!

Collapse
 
utkarshyadav profile image
Utkarsh Yadav • Edited

Great! need this very much.

Collapse
 
yvettelau profile image
YvetteLau

Thanks for sharing!
I want to translate it into Chinese to help more developers, Can I get your authorization?

I will give credit at the top of the article.

Collapse
 
ibrahima92 profile image
Ibrahima Ndaw

Yes, you can translate it. Just make sure to give me credit.

Collapse
 
tcelestino profile image
Tiago Celestino

I saved in my Pocket and now I read and I can sau: great article. Thanks man!

Collapse
 
vishwamlr profile image
Vishwam Sirikonda

Very concise and useful, This is the best blog that I read in last one month. Thanks a lot

Collapse
 
zhuima profile image
追马

Thanks for sharing!