DS DevShelfHub Projects · AI tools
Cheatsheets / TypeScript
Cheatsheet · Languages

TypeScript: Types, Generics, Utility Types and Narrowing Reference Guide

By DevShelfHub

Types, generics, utility types, narrowing, modules, tsconfig — the syntax you actually look up under deadline.

87 items 7 min Types Generics Narrowing

Start hereQuick start · 6 you’ll reach for daily

Type a valueconst x: number = 1
Object shapetype T = { a: string }
Uniontype R = "ok" | "err"
Genericfunction id<T>(x: T): T
Keep literals{…} satisfies T
Runnpx tsx src/index.ts

Target versions · paceVersions

Targets: typescript ≥ 5.5 node ≥ 20 target: ES2022

Sheet assumes TypeScript 5.5+ — satisfies, const type parameters, noUncheckedIndexedAccess, NodeNext modules. For library authors, tsconfig’s declaration + composite matter most. Bun and Deno run TS without a separate compile step.

install · init · runSetup

bash
# New project (Node)
npm init -y
npm install -D typescript @types/node
npx tsc --init                        # generates tsconfig.json

# Direct run without a build step
npm install -D tsx
npx tsx src/index.ts                  # ts-node replacement, ESM-friendly

# Type check only (no emit)
npx tsc --noEmit

# Format + lint
npm install -D eslint typescript-eslint prettier
npx eslint . --fix

# Bun / Deno — first-class TS support, no install needed
bun run src/index.ts
deno run src/index.ts

where things liveCommon imports

The only thing you import from TypeScript itself is types. Everything else — node, zod, vitest — is JS that ships its types alongside.

import type { Foo } from "./foo"Type-only import. Erased at runtime.
import { type Foo, bar } from "./foo"Mixed: type modifier on the type member.
import { z } from "zod"Runtime + types in one package.
import { describe, it, expect } from "vitest"Default test runner.
import fs from "node:fs/promises"node: prefix for stdlib.
import express, { type Request, type Response } from "express"Library + its types from @types/express.
import path from "node:path"CommonJS default-import works because of esModuleInterop.

type the basicsPrimitives & syntax

string · number · boolean · bigint · symbolThe five JS primitives.
null · undefinedTwo flavours of “nothing”. strictNullChecks keeps them separate.
anyAvoid Opt-out of all checks. Almost never the right answer.
unknownPreferred Top type. Must narrow before use.
neverBottom type. Use for unreachable branches.
voidFunction returns nothing meaningful.
readonly T[]Immutable array view.
[string, number]Fixed-length tuple.
[string, ...number[]]Variadic tuple.
type Status = "ok" | "err"Literal union.
enum Color { R, G, B }Numeric enum. Prefer a const object + literal union for new code.
const COLORS = ["r","g","b"] as constLocks to literal tuple. Source for derived unions.
type Color = typeof COLORS[number]Derive a union from an array.

shape declarationstype vs interface

type T = { a: string }Most flexible. Unions, intersections, mapped, conditional.
interface I { a: string }Extendable. Declaration merging across files / libs.
interface I extends Base { … }Single-parent. Pull more in with intersections.
type T = A & BIntersection.
type T = A | BUnion.
type T = { readonly a: number; b?: string }Read-only + optional fields.
type T = Record<string, number>String-indexed map.
interface Window { __myFlag?: boolean }Augment a 3rd-party type via merging.
Default to type for new code. Reach for interface when you specifically want declaration merging (e.g. extending Express.Request).

type variablesGenerics

function id<T>(x: T): TType variable inferred from arg.
<T extends Bound>Constrain the parameter.
<K extends keyof T>Key parameter constrained to an object’s keys.
<T = string>Default. Used when inference fails.
function f<const T>(x: T)Const type parameter. Preserves literal narrowness.
type Map<T> = { [K in keyof T]: T[K] }Mapped type.
type Map<T> = { [K in keyof T as `get${K}`]: T[K] }Key remapping with template literals.
type R = T extends X ? Y : ZConditional type.
T extends (infer U)[] ? U : neverinfer peels out a sub-type.
type Distrib<T> = T extends any ? T[] : neverDistributive conditional over unions.
type X = [T] extends […] ? …Wrap in tuple to disable distribution.
typescript
// Generic function
function pluck(obj: T, key: K): T[K] {
  return obj[key];
}
pluck({ name: "Ada", age: 36 }, "name");   // string

// Generic class with default
class Box {
  constructor(public value: T) {}
}

// Conditional + infer — peel off the array element type
type ElementOf = T extends readonly (infer U)[] ? U : never;
type N = ElementOf;              // number

// Mapped type with key remapping
type Getters = {
  [K in keyof T as `get${Capitalize}`]: () => T[K];
};
type UserGetters = Getters<{ name: string; age: number }>;
// { getName: () => string; getAge: () => number }

// Branded / nominal types via intersection
type UserId = string & { readonly __brand: "UserId" };
const asUserId = (s: string): UserId => s as UserId;

stdlib of typesUtility types

Partial<T>All fields optional.
Required<T>All fields required.
Readonly<T>All fields readonly.
Pick<T, K>Keep specific keys.
Omit<T, K>Drop specific keys.
Record<K, V>Object map with key + value types.
Exclude<U, X> / Extract<U, X>Filter members of a union.
NonNullable<T>Drop null / undefined.
ReturnType<typeof fn>Extract return type of a function.
Parameters<typeof fn>Tuple of param types.
Awaited<T>Unwrap a Promise (recursively).
InstanceType<typeof Cls>Type of new Cls(…).
Uppercase<S> / Lowercase<S> / Capitalize<S>String literal transforms.

tell the compiler what you knowNarrowing

typeof x === "string"Narrow primitives.
x instanceof ErrorNarrow to a class.
"kind" in xin guard narrows a union.
switch (x.kind) { case … }Discriminated-union narrowing. The canonical pattern.
function isX(v): v is XUser-defined type guard.
function assertX(v): asserts v is XThrow-on-failure narrowing.
x!Non-null assertion. Use sparingly. Prefer real checks.
x as TCast. Bypass the checker; you own the lie.
satisfies TPreferred over annotation. Verifies shape, keeps literal types.
x as constLock to literal tuple / object.
typescript
// Discriminated union — the canonical pattern
type Shape =
  | { kind: "circle"; r: number }
  | { kind: "square"; side: number };

function area(s: Shape): number {
  switch (s.kind) {
    case "circle": return Math.PI * s.r ** 2;
    case "square": return s.side ** 2;
    default:       return assertNever(s);   // exhaustive
  }
}
function assertNever(x: never): never { throw new Error(`unreachable: ${x}`); }

// User-defined type guards
function isString(x: unknown): x is string {
  return typeof x === "string";
}

// Assertion functions
function assertDefined(x: T | undefined): asserts x is T {
  if (x === undefined) throw new Error("undefined");
}

// satisfies — keep literal types AND check shape
const config = {
  port: 3000,
  level: "info",
} satisfies { port: number; level: "info" | "warn" | "error" };
// type of config.level stays "info" (literal), not the union.

imports & declarationsModules

export const x = 1Named export.
export default fnDefault export. Use sparingly — tree-shaking + DX cost.
export * from "./foo"Re-export everything.
export { Foo as Bar } from "./foo"Rename on re-export.
import typeType-only import. Erased.
declare module "x" { … }Type-only ambient module (no @types pkg).
declare global { interface Window { … } }Augment globals.
/// <reference types="node" />Triple-slash reference. Mostly legacy.
.d.ts filesPure type definitions. Useful when shipping a library.

project knobstsconfig

"strict": trueMaster flag. Turns on the canonical seven strictness checks.
"noUncheckedIndexedAccess": trueHighly recommended Array / record access returns T | undefined.
"exactOptionalPropertyTypes": trueDistinguish missing from undefined.
"target": "ES2022"Output JS version. Match the runtime you ship to.
"module": "NodeNext"Native ESM/CJS resolution for modern Node.
"moduleResolution": "Bundler"Use with Vite / esbuild / Webpack toolchains.
"skipLibCheck": trueSkip checking .d.ts files. Always on for app code.
"isolatedModules": trueCompatibility with single-file transpilers (esbuild / swc).
"paths": { "@/*": ["src/*"] }Path aliases. Mirror in the bundler config.
"composite": true + project referencesRequired for incremental multi-project builds (monorepos).
json
{
  "compilerOptions": {
    /* Output */
    "target":             "ES2022",
    "module":             "NodeNext",
    "moduleResolution":   "NodeNext",
    "outDir":             "dist",
    "rootDir":            "src",
    "sourceMap":          true,
    "declaration":        true,

    /* Strictness — turn ALL of these on */
    "strict":                       true,
    "noUncheckedIndexedAccess":     true,
    "noImplicitOverride":           true,
    "noFallthroughCasesInSwitch":   true,
    "exactOptionalPropertyTypes":   true,
    "noImplicitReturns":            true,

    /* Hygiene */
    "skipLibCheck":               true,
    "esModuleInterop":            true,
    "forceConsistentCasingInFileNames": true,
    "isolatedModules":            true,

    /* Project paths */
    "baseUrl": "src",
    "paths": { "@/*": ["*"] }
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}

generics + zod + fetchEnd-to-end · Typed HTTP client

A tiny getJSON that takes a Zod schema and returns the inferred type — static safety from TS, runtime safety from Zod. ~25 lines, no framework.

typescript
// Tiny typed HTTP client — generics + Zod for runtime validation.
import { z } from "zod";

const User = z.object({
  id: z.number().int().positive(),
  name: z.string(),
  email: z.string().email(),
});
type User = z.infer;

class HttpError extends Error {
  constructor(public status: number, message: string) { super(message); }
}

async function getJSON(
  url: string,
  schema: S,
  init?: RequestInit,
): Promise> {
  const res = await fetch(url, init);
  if (!res.ok) throw new HttpError(res.status, await res.text());

  const data: unknown = await res.json();
  const parsed = schema.safeParse(data);
  if (!parsed.success) throw new Error(`Bad payload: ${parsed.error.message}`);
  return parsed.data;
}

const u: User = await getJSON("https://api.example.com/users/42", User);
console.log(u.email);                     // type-safe + runtime-validated

Best practiceGood to know

Use satisfies, not annotation, when you want literal types. const x = {…} satisfies T checks the shape and preserves the narrow literal types of each field — const x: T = {…} widens them.
Turn on noUncheckedIndexedAccess. Catches the entire family of “array element is undefined” bugs at compile time. One of the highest bug-per-letter strictness flags.
Validate untrusted input with Zod / Valibot, then trust the type. TypeScript checks the shape you tell it about, not what arrives at runtime. Parse at the boundary; the rest of the codebase stays clean.

Common trapsWatch out for

as is a lie. It tells the checker to trust you. If the runtime shape differs, errors land far from the cast. Reserve it for places where you genuinely know more than TS — library bridges, JSON boundary — and prefer satisfies elsewhere.
Default exports cause renaming chaos. import x from "./a" picks whatever name it wants. Auto-imports drift, refactors break silently. Prefer named exports.
Enums emit runtime code and don’t play with isolatedModules. Use as const + typeof X[keyof typeof X] instead. Same DX, zero JS output, friendlier to bundlers.

Go deeperSee also

TypeScript FAQ

What is TypeScript used for?

TypeScript is a statically typed superset of JavaScript that compiles to plain JavaScript. It adds type annotations, interfaces, generics, and strict null checks to catch errors at compile time rather than at runtime. It is used in React and Vue frontends, Node.js backends, and monorepos.

What is the difference between type and interface in TypeScript?

Both define the shape of an object. interface is open — you can merge declarations across files with the same name. type can express unions, intersections, mapped types, conditional types, and template literal types that interfaces cannot. For simple object shapes, either works; prefer type when you need advanced type manipulation.

What are TypeScript utility types?

Utility types are built-in generic types that transform existing types. Partial<T> makes all properties optional; Required<T> makes all required; Pick<T, K> keeps only listed keys; Omit<T, K> drops listed keys; Record<K, V> builds an index type; Readonly<T> bans mutation; ReturnType<F> extracts a function return type.

What is type narrowing in TypeScript?

Narrowing is how TypeScript refines a union or broad type to a specific one inside a conditional block. Common narrowing guards are typeof (for primitives), instanceof (for classes), in (for property existence), and custom type predicates (x is Dog). TypeScript tracks these through control flow analysis.

Is TypeScript free to use?

Yes. TypeScript is open source and maintained by Microsoft under the Apache 2.0 license. It is free for personal and commercial use, and the TypeScript compiler (tsc) ships as an npm package at no cost.