Typescript Cheat Sheet

Jakub Jankowski

Jakub Jankowski

TypeScript Engineer

Sometimes, it’s handy to have a simple reference to peek at and continue coding. If you want a refresher on types compatibility, syntax, interfaces, or other TypeScript nuances, please bookmark this cheat sheet! We’ll continue to update it with new hints over time.

Without further ado, let’s dive into it!

Types of types 🙂

Dynamic – types evaluated during runtime (JS, Python, Ruby)

let a = 1;
a = '1';

Static – type checking performed during compilation (types must be known, C, Java)

let a: number = 1;
a = '1'; // Error

Weak – types can be changed for a certain purpose (f.e. comparison): C, JS

const a = 4 - '3' // 1
const b = 4 + '3' // 43
'555' == 555 // true

const c = undefined;
c == null // true

Strong – types never change and are strictly compared – exception in TS if statements:

const a = 'a';
if (a) {} // exception

Structural – types must be structurally compatible

type A = { name: string };
type B = { name: string };
const a: A = { name: 'a' };
const b: B = a;

Nominal – compatibility by declaration (opposite to structural). Example: Java classes.

Types compatibility

Structural compatibility takes place for classes with only public fields. Static types aren’t compared; hence having different constructors and static fields doesn’t break compatibility

class A {
  id: number;
}
type B = {
  id: number;
}

const a = new A();
const b: B = a; // OK
const c: A = { id: 1 }; // OK
class A {
  static a = 1;
  constructor(id: number) {}
}
class B {
  static b = 'b'
  constructor(name: string) {}
}
const a: A = new B(1); // OK
const b: B = new A('a'); // OK

Object literal assignment is correct only for compatible types

type A = { a: number };
type B = { a: number; b: number };

const b: B = { a: 1, b: 2 };
const a1: A = b; // OK

const a2: A = { a: 1, b: 2 }; // Error

Subtype compatibility

type A = { a: number };
type B = { a: number; b: number };

let a: A = { a: 1 };
let b: B = { a: 1, b: 2 };

a = b;
b = a; // Error

Function compatibility

type A = { a: number };
type B = { a: number; b: number };

const a: A = { a: 1 };
const b: B = { a: 1; b: 2 };

function doA(a: A) {}
function doB(b: B) {}

doA(a); // OK
doA(b); // OK

doB(a); // Error
doB(b); // OK

let doAA = function(a: A) {}
let doBB = function(b: B) {}

doAA = doBB; // Error (doesn't apply to object methods - it passes there)
doBB = doAA; // OK

let doAAA = (): A => ({ a: 1 });
let doBBB = (): B => ({ a: 1, b: 2 });

doAAA = doBBB; // OK
doBBB = doAAA; // Error

type Opt = (a: number, b?: number) => void;
type Req = (a: number, b: number) => void;

let opt: Opt = (a, b) => {};
let req: Req = (a, b) => {};

opt = req; // Error
req = opt; // OK

Covariance – using subtype (extension of X) instead of a type X: Function return type is covariant

Contravariance – using supertype (more general than X) instead of type X Function argument type is contravariant

Bivariance – using subtype or supertype instead of type Object method argument type is bivariant

type A = { a: number };
type B = { a: number; b: number };
type C = { a: number; b: number; c: number };

type Fn = (x: B) => B;

type subtypeFn = (x: A) => C; // subtype of Fn

BivarianceHack – enables using bivariant types in funtions – object methods are bivariant so defining a type with a method and accessing it produces bivariant type:

type Bivariant<T> = {
  bivarianceHack(x: T): void;
}['bivarianceHack'];

Language

Merging interfaces with the same name making them easily extendable (usable for extending external libraries)

interface A {
  a: number;
}

interface A {
  b: number;
}

const a: A = {
  a: 1,
  b: 1,
}

Private fields in JS (#) and TS (private)

class A {
 private a: number; // removed during compilation (becomes public)
}

class B {
  #b: number; // remains private after compilation
}

Unique symbol – assigning a symbol to a const produces unique symbol type making it impossible to be compared and assigned (acting as nominal types)

const a: unique symbol = Symbol.for('a'); // same as typeof a

unions and intersections A | B – only common fields from both sets

A & B – all fields from both sets

as const – used for strings makes them literals, used for objects assigns a read-only attribute to its fields

const a = 'a' as const; // type 'a'

const obj = {
  a: 'a',
} as const;

typeof obj; // { readonly a: 'a' }

**asserts x is y'** - used as type guard similar to x is y’ but an assertion type for functions that throw an exception

function assertsIsA(a: any): asserts is A {
  if (!isA(a)) throw new Error();
}

typeof for types – returns type instead of a string with the type

const obj = { a: 1 };

type A = typeof obj; // { a: number }
const a = typeof obj; // "object"

typeof arr[number] – returns type of array content

const arr= [1, 'a'];
typeof arr[number] // string | number

obj[keyof obj] – returns type of each object fields

type obj = {
  a: number;
  b: string;
};

type a = obj[keyof obj]; // string | number

keyof any – returns types that can be used for object indexing

type Keys = K extends keyof any; // string | number | symbol

variadic tuples

type A = [...T, ...K];

Distributive conditional types – using conditional type on union is like using condition on each union component

type A = Condition<number | string | null>;
type B = Condition<number> | Condition<string> | Condition<null>;

Deferred conditional types – type remains conditional until its condition can be verified

declare function getY<T>(x: T): T extends Entity ? string : number;

function getData<U>(x: U) {
  return getY(x); // U extends Entity ? string : number
}

Conditional type – is compatible with a union of its results

declare function getY<T>(x: T): T extends Entity ? string : number;

function getData<U>(x: U) {
  const y = getY(x); // U extends Entity ? string : number
  const z: string | number = y; // OK
}

infer – infer the type and assign it to variable

function randomData(x: string) {
  return 5;
}
type ReturnType<T> = T extends (x: string) => infer R ? R : never;

type T = ReturnType<typeof randomData>; // number

Branding – enables using nominal typing (typescript has structural typing)

type A = {
  __brand: 'A';
  id: number;
};
type B = {
  __brand: 'B';
  id: number;
};

function getId(value: A) {
  return value.id;
}

declare const a: A;
declare const b: B;

getId(a); // OK
getId(b); // Error

type C = number & { __brand: 'C' };
type D = number & { __brand: 'D' };

declare let c: C;
declare let d: D;
c = d; // Error

Flavoring – enables using nominal types with optional literal so values can be assigned

type A = {
  __brand?: 'A';
  id: number;
};

Kudos to https://typescriptnapowaznie.pl/ and https://typescript-book.com/ for being an inspiration to write this post!