Skip to content

Instantly share code, notes, and snippets.

@ruizb
Last active January 17, 2025 13:06
Show Gist options
  • Select an option

  • Save ruizb/55e1fc37cb198dccfdaf81450c3ebd43 to your computer and use it in GitHub Desktop.

Select an option

Save ruizb/55e1fc37cb198dccfdaf81450c3ebd43 to your computer and use it in GitHub Desktop.
A glossary of TypeScript.

A glossary of TypeScript

Motivation

Once upon a time, there was a developer that had an issue with TypeScript. He wanted to share his issue with the community, but he didn't know how to properly write the title that would best describe his problem. He struggled to find the appropriate words as he didn't know how to name "this behavior" or "that kind of type mechanism". After hours of reflexion, he ended up posting an issue with a pretty awkward title.

This story encouraged me to start writing a glossary of TypeScript. Hopefully it will help you if you have to write an issue or communicate with other TypeScript developers.

Disclaimer: it was me, I was the developer that struggled. I still struggle though, but this means I still have things to learn, which is great!

You may not agree with some definitions or examples in this document. Please, feel free to leave a comment and let me know, only together can we improve this glossary! Also, if the stars are aligned and this gist gets more and more attention, I might move it into its own repository so we can all contribute via issues and pull-requests! 🚀

Table on content

Glossary

String literal type

String literal types were introduced in TypeScript v1.8. It is meant to expect only a specific set of strings, instead of "any string".

Example
type Scheme = 'http' | 'https'

const prependWithScheme = (scheme: Scheme, domain: string, path: string): string =>
  `${scheme}://${domain}/${path}`

prependWithScheme('http')
prependWithScheme('https')
prependWithScheme('')

Here, no TypeScript error is raised for prependWithScheme('http') and prependWithScheme('https'). Even better, the IDE knows which values are available for autocompletion. However, an error is raised for prependWithScheme('') as the empty string '' is not availalbe in the string literal type Scheme.

Union type

Union types were introduced in TypeScript v1.4. It's a way of expressing mulitple possible types for a given value.

Example
declare const value: string | number | boolean

Here, value type can be either string, number or boolean.

Discriminated union

Discriminated unions were introduced in TypeScript 2.0 as "tagged union types".

Example
interface Square {
  kind: 'square'
  size: number
}

interface Rectangle {
  kind: 'rectangle'
  width: number
  height: number
}

interface Circle {
  kind: 'circle'
  radius: number
}

type Shape = Square | Rectangle | Circle

Here, Shape is the discriminated union, where the discriminant - also known as singleton property or tag - is the kind property.

You can also check the official documentation section on discriminated unions.

Intersection type

Intersection types were introduced in TypeScript v1.6 as a complement to union types. This allows us to type a value that is both a A and a B.

Example
interface WithName {
  name: string
}

interface WithAge {
  age: number
}

type User = WithName & WithAge

The User type is computed as { name: string, age: number }, which is the intersection of both types WithName and WithAge.

The intersection of types that have nothing in common results in the never type, introduced in TypeScript v2.0.

Example
type A = 'a' & 'b'

type B = { name: string, age: number } & { name: number, adult: boolean }

Here, A is never, because the string literals 'a' and 'b' have nothing in common.

For B, its type results in { name: never, age: number, adult: boolean } because string and number have nothing in common. As one of its properties type is never, there's no way to create a value which type is B, because no value can be assigned to the never type.

Type guard

A type guard, whether it's built into the TypeScript language or provided by the developer, allows for a type to be narrowed in a conditional branch.

Example
declare const value: string | number

const getNumberOfChars = (s: string): number => s.length

const isGreaterThan = (n: number, bound: number): boolean => n > bound

if (typeof value === 'string') {
  console.log(`Number of chars in ${value}: ${getNumberOfChars(value)}.`)
} else if (typeof value === 'number') {
  console.log(`Is ${value} greater than 20? ${isGreaterThan(value, 20) ? 'Yes' : 'No'}.`)
} else {
  throw 'Impossible, perhaps the archives are incomplete'
}

By checking the type of value with typeof, TypeScript knows that in the if branch, value must have that type and not the others:

  • In the if (typeof value === 'string') { ... } branch, value type is narrowed from string | number to string.
  • In the if (typeof value === 'number') { ... } branch, value type is narrowed from string | number to number.
  • In the else { ... } branch, since all the possible types have been checked in the previous if conditions, value type is narrowed from string | number to never (it "can't" happen since all the possible cases have been checked already).

The developer can also create its own type guards thanks to the is syntax.

Example
interface User {
  name: string
  age: number
}

// You can ignore this function for the sake of this example
const hasOwnProperty = <A extends {}, B extends PropertyKey>(obj: A, prop: B): obj is A & Record<B, unknown> =>
  obj.hasOwnProperty(prop)

const isUser = (v: unknown): v is User =>
  typeof v === 'object' &&
  v !== null &&
  hasOwnProperty(v, 'name') &&
  typeof v.name === 'string' &&
  hasOwnProperty(v, 'age') &&
  typeof v.age === 'number'

declare const value: unknown

if (isUser(value)) {
  console.log(`User(name = ${value.name}, age = ${value.age})`)
} else {
  throw `Invalid user provided: ${JSON.stringify(value)}`
}

We created the v is User type guard as the return value of the isUser function, which tells TypeScript that if isUser(value) returns true, then value is a User in the if branch, otherwise keep the initial type, which is unknown here.

You can also check the official documentation about type guards.

Type narrowing

It's the ability for the TypeScript language to restrict the type of a value to a subset of that type.

Example
type A =
  | { kind: 'a', arg1: 'hey' }
  | { kind: 'b', arg1: 'Hello', arg2: 'World' }
  | { kind: 'c' }

declare const value: A

if (value.kind === 'a') {
  console.log(`Value of kind ${value.kind} has argument ${value.arg1}`)
} else if (value.kind === 'b') {
  console.log(`Value of kind ${value.kind} has arguments ${value.arg1} and ${value.arg2}`)
} else {
  console.log(`Value of kind ${value.kind} has no argument`)
}
  • If value.kind === 'a' is true, then value type is { kind: 'a', arg1: 'hey' }
  • Otherwise, if value.kind === 'b' is true, then value type is { kind: 'b', arg1: 'Hello', arg2: 'World' }
  • Otherwise, since we've already handled the cases { kind: 'a', arg1: 'hey' } and { kind: 'b', arg1: 'Hello', arg2: 'World' }, only the last one of the discriminated union A is left, i.e. { kind: 'c' }

More examples are available in the type guard section.

Mapped type

Mapped types were introduced in TypeScript v2.1. They have been improved with further releases, allowing the creation of pretty complex types. Essentially, mapped types can be used as functions for the types, allowing a developer to transform types into other types. TypeScript provides several mapped types such as Required, Partial, Exclude, Pick and Omit for example.

Example
interface Config {
  address: string
  port: number
  logsLevel: 'none' | 'error' | 'warning' | 'info' | 'debug'
}

type A = Partial<Config>
type B = Omit<Config, 'address' | 'logsLevel'>
type C = Pick<Config, 'logsLevel'>

Types A, B and C are all transformations of Config thanks to the mapped types Partial, Omit and Pick:

  • A is computed as:

    {
      address?: string | undefined,
      port?: number | undefined,
      logslevel?: 'none' | 'error' | 'warning' | 'info' | 'debug' | undefined
    }
  • B is computed as:

    {
      port: number
    }
  • C is computed as:

    {
      logslevel: 'none' | 'error' | 'warning' | 'info' | 'debug' | undefined
    }

Diagnostic message

When TypeScript detects an error/warning in your program, its checker (an internal component of the language) generates a diagnostic message.

Example
// { "strict": true }

const value: string = undefined
Type 'undefined' is not assignable to type 'string'. (2322)

The 2322 number is an ID that could be used to reference the type of diagnostic message when creating an issue for example. More specifically, the 2322 ID relates to the Type '{0}' is not assignable to type '{1}'. diagnostic. You can also check the wiki for more information about these messages.

@nousernames2
Copy link
Copy Markdown

Thanks for taking the time to put this together. +1

@Ellen010
Copy link
Copy Markdown

Valuable resource, very apprecited!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment