TypeScript Quick Reference

Type basics, interfaces, generics, enums, utility types, type guards, decorators and tsconfig.json.

Type Basics

// Primitive type annotations
let name: string = 'Alex';
let age: number = 30;
let active: boolean = true;
let id: bigint = 9007199254740991n;
let sym: symbol = Symbol('unique');

// Arrays
let numbers: number[] = [1, 2, 3];
let names: Array<string> = ['Alice', 'Bob'];

// Tuples — fixed-length arrays with specific types per position
let point: [number, number] = [10, 20];
let entry: [string, number] = ['count', 42];
let labeled: [id: number, name: string] = [1, 'Alex'];  // labeled tuple

// Special types
let anything: any = 'no type checking';      // escapes type system
let unknown: unknown = getData();             // safer than any — must narrow before use
let nothing: void = undefined;               // functions that don't return
let impossible: never = throwError();         // functions that never return

// Type assertion — tell the compiler you know better
const input = document.getElementById('name') as HTMLInputElement;
const canvas = <HTMLCanvasElement>document.getElementById('canvas');

// Literal types
let direction: 'up' | 'down' | 'left' | 'right' = 'up';
let httpStatus: 200 | 404 | 500 = 200;

// Nullability
let optional: string | null = null;
let maybeUndefined: string | undefined = undefined;

// Non-null assertion (use sparingly)
const el = document.getElementById('app')!;  // asserts non-null
TypeDescriptionExample
stringText data'hello'
numberAll numbers (int + float)42, 3.14
booleanTrue/falsetrue
anyOpts out of type checkingAvoid when possible
unknownType-safe any — must narrowPrefer over any
voidNo return value(): void
neverUnreachable codeExhaustive checks
null / undefinedAbsence of valueEnable strictNullChecks

Interfaces

// Basic interface
interface User {
  id: number;
  name: string;
  email: string;
  role?: string;             // optional property
  readonly createdAt: Date;  // cannot be reassigned
}

const user: User = {
  id: 1,
  name: 'Alex',
  email: '[email protected]',
  createdAt: new Date()
};

// Method signatures
interface Logger {
  log(message: string): void;
  warn(message: string): void;
  error(message: string, code?: number): void;
}

// Index signatures — dynamic keys
interface StringMap {
  [key: string]: string;
}

interface NumberDict {
  [key: string]: number;
  length: number;  // named properties must match the index type
}

// Extending interfaces
interface Animal {
  name: string;
  sound(): string;
}

interface Dog extends Animal {
  breed: string;
  fetch(): void;
}

// Extending multiple interfaces
interface ServiceWorker extends Employee, Contractor {
  serviceId: string;
}

// Declaration merging — interfaces with the same name are merged
interface Config {
  apiUrl: string;
}
interface Config {
  timeout: number;
}
// Config now has both apiUrl and timeout

// Callable interface
interface Formatter {
  (input: string): string;
  locale: string;
}

// Class implementing an interface
class AdminUser implements User {
  id: number;
  name: string;
  email: string;
  readonly createdAt: Date;
  permissions: string[];

  constructor(id: number, name: string, email: string) {
    this.id = id;
    this.name = name;
    this.email = email;
    this.createdAt = new Date();
    this.permissions = [];
  }
}

Type Aliases & Unions

// Type alias — name for any type
type ID = string | number;
type Coordinate = [number, number];
type Callback = (data: unknown) => void;

// Union types — a value that can be one of several types
type Status = 'idle' | 'loading' | 'success' | 'error';
type Result = string | number | boolean;

function formatId(id: string | number): string {
  return typeof id === 'string' ? id : id.toString();
}

// Intersection types — combine multiple types
type Timestamped = {
  createdAt: Date;
  updatedAt: Date;
};

type UserRecord = User & Timestamped;

// Discriminated unions — the recommended pattern for variant types
type Shape =
  | { kind: 'circle'; radius: number }
  | { kind: 'rectangle'; width: number; height: number }
  | { kind: 'triangle'; base: number; height: number };

function area(shape: Shape): number {
  switch (shape.kind) {
    case 'circle':
      return Math.PI * shape.radius ** 2;
    case 'rectangle':
      return shape.width * shape.height;
    case 'triangle':
      return (shape.base * shape.height) / 2;
  }
}

// Template literal types
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';
type ApiRoute = `/api/${string}`;
type EventName = `on${Capitalize<string>}`;

// Conditional types
type IsString<T> = T extends string ? true : false;
type A = IsString<'hello'>;  // true
type B = IsString<42>;       // false

// Mapped types
type ReadonlyUser = {
  readonly [K in keyof User]: User[K];
};

type OptionalUser = {
  [K in keyof User]?: User[K];
};

Generics

// Generic function
function identity<T>(value: T): T {
  return value;
}

const str = identity('hello');   // type: string
const num = identity(42);        // type: number

// Generic with constraint
function getLength<T extends { length: number }>(item: T): number {
  return item.length;
}

getLength('hello');     // 5
getLength([1, 2, 3]);  // 3

// Generic interface
interface ApiResponse<T> {
  data: T;
  status: number;
  message: string;
}

const userRes: ApiResponse<User> = {
  data: { id: 1, name: 'Alex', email: '[email protected]', createdAt: new Date() },
  status: 200,
  message: 'OK'
};

// Generic class
class Stack<T> {
  private items: T[] = [];

  push(item: T): void {
    this.items.push(item);
  }

  pop(): T | undefined {
    return this.items.pop();
  }

  peek(): T | undefined {
    return this.items[this.items.length - 1];
  }

  get size(): number {
    return this.items.length;
  }
}

const numStack = new Stack<number>();
numStack.push(10);

// Multiple type parameters
function merge<A, B>(a: A, b: B): A & B {
  return { ...a, ...b } as A & B;
}

// Default type parameter
interface PaginatedResult<T = unknown> {
  items: T[];
  page: number;
  totalPages: number;
}

// keyof and indexed access
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

const userName = getProperty(user, 'name');  // type: string

Enums

// Numeric enum (auto-increments from 0)
enum Direction {
  Up,       // 0
  Down,     // 1
  Left,     // 2
  Right     // 3
}

// Numeric enum with custom start
enum HttpStatus {
  OK = 200,
  Created = 201,
  BadRequest = 400,
  NotFound = 404,
  ServerError = 500
}

const status: HttpStatus = HttpStatus.OK;

// String enum — prefer these for readability and debugging
enum Role {
  Admin = 'ADMIN',
  Editor = 'EDITOR',
  Viewer = 'VIEWER'
}

function checkAccess(role: Role): boolean {
  return role === Role.Admin || role === Role.Editor;
}

// Const enum — inlined at compile time (no runtime object)
const enum LogLevel {
  Debug = 'DEBUG',
  Info = 'INFO',
  Warn = 'WARN',
  Error = 'ERROR'
}

// Reverse mapping (numeric enums only)
enum Color {
  Red,
  Green,
  Blue
}
Color[0];        // 'Red'
Color['Red'];    // 0
Prefer string enums or union literal types (type Role = 'admin' | 'editor') over numeric enums for better debugging and type safety. Const enums produce smaller output but can't be iterated at runtime.

Utility Types

UtilityDescriptionExample
Partial<T>All properties optionalPartial<User>
Required<T>All properties requiredRequired<Config>
Readonly<T>All properties readonlyReadonly<State>
Pick<T, K>Select specific keysPick<User, 'id' | 'name'>
Omit<T, K>Remove specific keysOmit<User, 'password'>
Record<K, V>Object type with key/valueRecord<string, number>
Exclude<T, U>Remove types from unionExclude<Status, 'error'>
Extract<T, U>Extract types from unionExtract<Status, 'idle' | 'loading'>
NonNullable<T>Remove null/undefinedNonNullable<string | null>
ReturnType<T>Return type of functionReturnType<typeof fn>
Parameters<T>Param types as tupleParameters<typeof fn>
Awaited<T>Unwrap Promise typeAwaited<Promise<string>>
// Practical usage
interface User {
  id: number;
  name: string;
  email: string;
  password: string;
}

// Update payload — all fields optional
type UpdateUser = Partial<User>;

// Public-facing user — no password
type PublicUser = Omit<User, 'password'>;

// Summary — only specific fields
type UserSummary = Pick<User, 'id' | 'name'>;

// Lookup table
type UserMap = Record<number, User>;

// Combine utilities
type CreateUser = Omit<User, 'id'> & { role?: string };

// Deep readonly (recursive)
type DeepReadonly<T> = {
  readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K];
};

Type Guards

// typeof guard
function format(value: string | number): string {
  if (typeof value === 'string') {
    return value.toUpperCase();    // narrowed to string
  }
  return value.toFixed(2);          // narrowed to number
}

// instanceof guard
function handleError(err: Error | string): string {
  if (err instanceof Error) {
    return err.message;             // narrowed to Error
  }
  return err;                        // narrowed to string
}

// in operator guard
interface Fish { swim(): void; }
interface Bird { fly(): void; }

function move(animal: Fish | Bird) {
  if ('swim' in animal) {
    animal.swim();                  // narrowed to Fish
  } else {
    animal.fly();                   // narrowed to Bird
  }
}

// Custom type guard with type predicate
function isString(value: unknown): value is string {
  return typeof value === 'string';
}

function isUser(obj: unknown): obj is User {
  return (
    typeof obj === 'object' &&
    obj !== null &&
    'id' in obj &&
    'name' in obj &&
    'email' in obj
  );
}

// Discriminated union guard
type ApiResult =
  | { status: 'success'; data: User[] }
  | { status: 'error'; message: string };

function handle(result: ApiResult) {
  if (result.status === 'success') {
    console.log(result.data);       // narrowed — data is available
  } else {
    console.error(result.message);  // narrowed — message is available
  }
}

// Assertion function (throws if condition is false)
function assertDefined<T>(value: T | undefined, msg: string): asserts value is T {
  if (value === undefined) throw new Error(msg);
}

const maybeUser: User | undefined = getUser();
assertDefined(maybeUser, 'User not found');
maybeUser.name;  // safe — type is now User

Decorators

Decorators require "experimentalDecorators": true in tsconfig.json (legacy) or the TC39 Stage 3 decorator syntax (TS 5.0+). The examples below use the legacy syntax commonly seen in Angular, NestJS and similar frameworks.
// Class decorator
function Sealed(constructor: Function) {
  Object.seal(constructor);
  Object.seal(constructor.prototype);
}

@Sealed
class Greeter {
  greeting: string;
  constructor(message: string) {
    this.greeting = message;
  }
}

// Decorator factory — returns a decorator
function Logger(prefix: string) {
  return function (target: Function) {
    console.log(`${prefix}: ${target.name} registered`);
  };
}

@Logger('API')
class UserController { }

// Method decorator
function Log(target: any, key: string, descriptor: PropertyDescriptor) {
  const original = descriptor.value;
  descriptor.value = function (...args: any[]) {
    console.log(`Calling ${key} with`, args);
    const result = original.apply(this, args);
    console.log(`${key} returned`, result);
    return result;
  };
}

class MathService {
  @Log
  add(a: number, b: number): number {
    return a + b;
  }
}

// Property decorator
function MinLength(min: number) {
  return function (target: any, key: string) {
    let value: string;
    Object.defineProperty(target, key, {
      get: () => value,
      set: (newVal: string) => {
        if (newVal.length < min) {
          throw new Error(`${key} must be at least ${min} characters`);
        }
        value = newVal;
      }
    });
  };
}

class Account {
  @MinLength(3)
  username!: string;
}

Module System

// Named exports
export const API_URL = 'https://api.example.com';
export function fetchData(url: string): Promise<Response> {
  return fetch(url);
}
export interface Config {
  apiUrl: string;
  timeout: number;
}

// Default export
export default class UserService {
  async getAll(): Promise<User[]> { /* ... */ }
}

// Re-export
export { fetchData } from './api';
export { default as UserService } from './UserService';
export * from './types';
export * as utils from './utils';

// Import
import UserService from './UserService';
import { API_URL, fetchData } from './api';
import type { Config } from './config';        // type-only import (erased at runtime)
import { type User, createUser } from './user'; // inline type import

// Dynamic import
const module = await import('./heavy-module');

// Namespace (avoid in modern code — prefer modules)
namespace Validation {
  export function isEmail(s: string): boolean {
    return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(s);
  }
}

// Ambient declarations — for untyped JS libraries
declare module 'legacy-lib' {
  export function doThing(input: string): void;
}

// Global augmentation
declare global {
  interface Window {
    analytics: AnalyticsAPI;
  }
}

tsconfig.json Reference

{
  "compilerOptions": {
    // Language and environment
    "target": "ES2022",            // JS output version
    "lib": ["ES2022", "DOM"],      // available type declarations
    "module": "ESNext",            // module system for output
    "moduleResolution": "bundler", // how imports are resolved

    // Strictness — enable all for new projects
    "strict": true,                // enables all strict flags below
    "strictNullChecks": true,      // null/undefined not assignable to other types
    "strictFunctionTypes": true,   // stricter function type checking
    "noImplicitAny": true,         // error on implicit any
    "noImplicitReturns": true,     // error if not all paths return
    "noUncheckedIndexedAccess": true, // adds undefined to index signatures
    "exactOptionalPropertyTypes": true,

    // Output
    "outDir": "./dist",
    "rootDir": "./src",
    "declaration": true,           // generate .d.ts files
    "declarationMap": true,        // source maps for .d.ts
    "sourceMap": true,             // generate .js.map files
    "removeComments": true,

    // Interop
    "esModuleInterop": true,       // allows default imports from CJS
    "allowSyntheticDefaultImports": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,     // import .json files
    "isolatedModules": true,       // required for Babel/SWC/esbuild

    // Path aliases
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"],
      "@components/*": ["src/components/*"],
      "@utils/*": ["src/utils/*"]
    },

    // Decorators
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,

    // Performance
    "skipLibCheck": true,
    "incremental": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist", "**/*.test.ts"]
}
FlagPurpose
"strict": trueEnables all strict type-checking options at once
"noEmit": trueType-check only, don't produce JS output (useful with bundlers)
"isolatedModules": trueRequired when using Babel, SWC, or esbuild for compilation
"skipLibCheck": trueSkip type checking of declaration files for faster builds
"moduleResolution": "bundler"Modern resolution for Vite, webpack, esbuild projects