TypeScript Expert Revision Handbook

📑 Table of Contents

Fundamentals
Advanced Types
Generics in Depth
Type Inference & Compatibility
Type Safety in JavaScript Interop
Type Guards & Narrowing
Advanced Patterns
TypeScript in React
Performance & Scaling
Ecosystem &am…


This content originally appeared on DEV Community and was authored by Animesh Pandey

📑 Table of Contents

  • Fundamentals
  • Advanced Types
  • Generics in Depth
  • Type Inference & Compatibility
  • Type Safety in JavaScript Interop
  • Type Guards & Narrowing
  • Advanced Patterns
  • TypeScript in React
  • Performance & Scaling
  • Ecosystem & Future

🟦 Fundamentals (Expert Level)

1. 🧩 Type System vs Runtime Behavior

Definition:
TypeScript is a compile-time type system — all types are erased at runtime.

✅ Key Points

  • Types are structural, not nominal (duck typing).
  • Type checking = compile-time only.
  • At runtime, it’s just JavaScript.

⚠️ Gotchas

  • Type safety doesn’t prevent runtime errors:
function greet(name: string) {
  return "Hello " + name.toUpperCase();
}
greet((123 as unknown) as string); // compiles, but runtime crash
  • No runtime enforcement — you need runtime validators (zod, io-ts, yup).

🎯 Interview One-Liner

“TypeScript’s types disappear at runtime. They make dev-time guarantees, but runtime safety requires explicit validation.”

2. 🔢 Core Primitive Types

✅ Basic Types

  • string, number, boolean
  • null, undefined
  • symbol, bigint

✅ Special Types

  • any → Opt-out of type system (unsafe).
  • unknown → Safer alternative; must narrow before use.
  • never → Function that never returns (errors, infinite loops).
  • void → Function returns nothing.

⚠️ Gotchas

let x: any = 42;
x.toUpperCase(); // compiles, runtime crash

let y: unknown = 42;
y.toUpperCase(); // ❌ compile error

🎯 Interview One-Liner

any disables safety. Prefer unknown when you don’t know the type, since it forces narrowing before use.”

3. 🏷️ Type Assertions vs Casting

Definition:
Tell the compiler “trust me, this is of type X.”

✅ Syntax

const el = document.getElementById("foo") as HTMLDivElement;

✅ Non-null Assertion

const el = document.getElementById("foo")!; // never null

⚠️ Gotchas

  • Overusing assertions bypasses safety.
  • Wrong assertions → runtime crashes.
  • Non-null ! can hide real null bugs.

🎯 Interview One-Liner

“Assertions tell TS to trust you. They don’t change runtime — overuse can hide real errors.”

4. 🔍 Type Narrowing (Control Flow Analysis)

Definition:
TS refines types based on runtime checks.

✅ Techniques

  • typeof
  • instanceof
  • Equality checks
  • Discriminated unions (tag property)

Example

function printLen(x: string | string[]) {
  if (typeof x === "string") console.log(x.length);
  else console.log(x.length); // string[] length
}

⚠️ Gotchas

  • Narrowing only works if TS can see the check.
  • External function calls won’t narrow unless you define type predicates.
function isString(x: unknown): x is string {
  return typeof x === "string";
}

🎯 Interview One-Liner

“TypeScript narrows unions using control flow. You can extend it with custom type predicates.”

5. 🚦 Strict Null Checking

Definition:
With strictNullChecks, null and undefined are not assignable to other types unless explicitly included.

✅ Example

let x: string = null; // ❌ error under strictNullChecks
let y: string | null = null; // ✅

✅ Optional Chaining + Nullish Coalescing

const name = user?.profile?.name ?? "Guest";

⚠️ Gotchas

  • Many JS libs don’t account for undefined.
  • Without strictNullChecks, you get unsound behavior (nullable everywhere).

🎯 Interview One-Liner

“Strict null checks make nullability explicit, avoiding the billion-dollar mistake. Combine with optional chaining and nullish coalescing.”

6. 🔎 Structural Typing (vs Nominal)

Definition:
TS uses structural typing — compatibility is based on shape, not declared type.

✅ Example

type Point = { x: number; y: number };
type Coord = { x: number; y: number };

let a: Point = { x: 1, y: 2 };
let b: Coord = a; // ✅ works (same shape)

⚠️ Gotchas

  • Extra properties are rejected in object literals:
let p: Point = { x: 1, y: 2, z: 3 }; // ❌ error
  • But extra props are allowed if passed as variable:
const tmp = { x: 1, y: 2, z: 3 };
let p: Point = tmp; // ✅ works

🎯 Interview One-Liner

“TypeScript is structurally typed — if it quacks like a duck, it’s assignable. But object literals have stricter excess property checks.”

7. 📝 Type Aliases vs Interfaces

Definition:
Two ways to define object types.

✅ Differences

  • Interface: extendable via declaration merging.
  • Type Alias: supports unions, intersections, primitives.

Example

interface A { x: number }
interface A { y: number } // merged
type B = { x: number } & { y: number } // no merging, must intersect

⚠️ Gotchas

  • Prefer interface for public APIs (extendable).
  • Prefer type for unions and complex compositions.

🎯 Interview One-Liner

“Interfaces are open (mergeable), types are closed but more flexible (unions, intersections). Use each where it fits.”

8. 📦 Enums vs Literal Types

✅ Enums

enum Direction { Up, Down }

✅ Literal Unions

type Direction = "up" | "down";

⚠️ Gotchas

  • String literal unions are usually better (type-safe, no runtime overhead).
  • Enums generate runtime objects → heavier.
  • const enum is inlined at compile time but can break tooling.

🎯 Interview One-Liner

“Prefer literal unions over enums — they’re lighter and more type-safe. Use enums only when runtime mapping is required.”

🟦 Advanced Types

1. ➕ Union & Intersection Types

✅ Union (|)

  • Represents either type.
type Input = string | number;
let val: Input = "hi";  // ✅
val = 42;               // ✅

✅ Intersection (&)

  • Combines multiple types.
type Person = { name: string };
type Worker = { company: string };
type Employee = Person & Worker; 
// { name: string; company: string }

⚠️ Gotchas

  • Union narrows to shared members:
function len(x: string | string[]) {
  return x.length; // works (length exists on both)
}
  • Intersection of incompatible types → never:
type Impossible = string & number; // never

🎯 One-Liner

“Unions = OR, intersections = AND. Incompatible intersections collapse to never.”

2. 🔢 Literal Types & Const Assertions

✅ Literal Types

let dir: "up" | "down";
dir = "up"; // ✅
dir = "left"; // ❌

as const

  • Locks values into literal types.
const colors = ["red", "green"] as const;
type Color = typeof colors[number]; // "red" | "green"

🎯 One-Liner

“Literal types restrict values to exact strings/numbers. as const freezes arrays/objects for inference.”

3. 📝 Template Literal Types

✅ Key Points

  • Build strings at type level.
type Event = `on${Capitalize<"click" | "hover">}`;
// "onClick" | "onHover"

✅ Use Cases

  • Event handler names.
  • Typed CSS props.
  • API route patterns.

🎯 One-Liner

“Template literal types compose strings at type level — great for event names, routes, and API typings.”

4. 🧮 Conditional Types

Definition:
T extends U ? X : Y

✅ Example

type IsString<T> = T extends string ? true : false;
type A = IsString<"hi">;   // true
type B = IsString<number>; // false

✅ Distributive Behavior

type ToArray<T> = T extends any ? T[] : never;
type C = ToArray<string | number>; // string[] | number[]

⚠️ Gotchas

  • Distribution only happens on naked type parameters.
  • Use brackets to disable:
type NoDistrib<T> = [T] extends [any] ? T[] : never;

🎯 One-Liner

“Conditional types let you branch at type level. By default they distribute over unions.”

5. 🗂️ Mapped Types

✅ Basics

type OptionsFlags<T> = {
  [K in keyof T]: boolean;
};
type Features = { darkMode: () => void };
type Flags = OptionsFlags<Features>; 
// { darkMode: boolean }

✅ Modifiers

  • readonly
  • ? optional
  • -readonly / -? to remove
type Mutable<T> = { -readonly [K in keyof T]: T[K] };

🎯 One-Liner

“Mapped types iterate over keys to transform shape — add/remove optionality, readonly, etc.”

6. 🛠️ Utility Types

✅ Built-ins

  • Partial<T> → all optional
  • Required<T> → all required
  • Readonly<T> → all readonly
  • Pick<T, K> → subset
  • Omit<T, K> → all except
  • Record<K, V> → dict type
  • ReturnType<T> → infer fn return
  • Parameters<T> → tuple of args
  • InstanceType<T> → type of new T()

Example

type Todo = { id: number; title: string; done?: boolean };
type TodoDraft = Partial<Todo>; // all optional

🎯 One-Liner

“Utility types like Partial, Pick, Omit, Record abstract common transformations of object types.”

7. 🧩 Key Remapping in Mapped Types (TS 4.1+)

type Prefix<T> = {
  [K in keyof T as `on${Capitalize<string & K>}`]: T[K]
};
type Events = Prefix<{ click: () => void }>;
// { onClick: () => void }

🎯 One-Liner

“Mapped types can rename keys dynamically using as clauses and template literals.”

8. 🌀 Recursive & Deep Types

✅ Recursive Types

type JSONValue = string | number | boolean | JSONValue[] | { [k: string]: JSONValue };

✅ Deep Utility

type DeepPartial<T> = { [K in keyof T]?: DeepPartial<T[K]> };

⚠️ Gotchas

  • Deep recursion can slow compiler dramatically.
  • Use cautiously in very large codebases.

🎯 One-Liner

“Recursive types enable JSON-like structures and deep utilities, but may hit compiler perf limits.”

🟦 Generics in Depth

1. 🧩 Generic Functions

Definition:
Functions parameterized with type variables.

✅ Example

function identity<T>(arg: T): T {
  return arg;
}
const a = identity<string>("hello");
const b = identity(42); // inferred as number

🎯 One-Liner

“Generics make functions reusable across types, with inference for convenience.”

2. 🔒 Constraints (extends)

Restrict generics to a subset of types.

✅ Example

function getLength<T extends { length: number }>(x: T) {
  return x.length;
}
getLength("hi");       // ✅
getLength([1,2,3]);    // ✅
getLength(42);         // ❌

🎯 One-Liner

“Constraints limit generics with extends, allowing access to known members.”

3. 🏷️ Generic Interfaces & Classes

✅ Interfaces

interface Box<T> { value: T }
const stringBox: Box<string> = { value: "hi" };

✅ Classes

class Container<T> {
  constructor(public value: T) {}
}
const c = new Container<number>(123);

🎯 One-Liner

“Generics extend beyond functions — interfaces and classes can also be parameterized.”

4. ⚖️ Default Generic Parameters

Provide defaults for flexibility.

✅ Example

interface ApiResponse<T = any> {
  data: T;
  error?: string;
}
const res: ApiResponse = { data: "hi" }; // T defaults to any

🎯 One-Liner

“Defaults reduce boilerplate when generic type can be inferred or safely assumed.”

5. 🔄 Keyof & Indexed Access with Generics

✅ Keyof

type Keys<T> = keyof T;
type User = { id: number; name: string };
type UKeys = Keys<User>; // "id" | "name"

✅ Indexed Access

type Value<T, K extends keyof T> = T[K];
type NameType = Value<User, "name">; // string

🎯 One-Liner

“Keyof + indexed access lets you make type-safe utilities (like Pick/Omit).”

6. 🎭 Conditional Generics

Generics inside conditional types = super powerful.

✅ Example

type Flatten<T> = T extends any[] ? T[number] : T;
type A = Flatten<string[]>; // string
type B = Flatten<number>;   // number

🎯 One-Liner

“Conditional generics let you branch inside generics — e.g., flatten arrays, unwrap promises.”

7. ⚖️ Variance (Covariance vs Contravariance)

Definition:
How subtyping interacts with generics.

✅ Covariance

  • Safe to use subtype in place of supertype.
let str: string = "hi";
let val: string | number = str; // ✅

✅ Contravariance

  • Function parameters are contravariant:
type Fn<T> = (x: T) => void;
let fn: Fn<string | number> = (x: string) => {}; // ✅

⚠️ Gotchas

  • TypeScript uses bivariant function parameters by default for compatibility (unsafe but practical).
  • --strictFunctionTypes enforces contravariance.

🎯 One-Liner

“Function parameters are contravariant, return types are covariant. TS is bivariant by default unless strictFunctionTypes is on.”

8. 🌀 Higher-Kinded Types (HKTs, Workarounds)

TypeScript doesn’t support true HKTs (types parameterized over type constructors), but you can simulate.

✅ Example

interface Functor<F> {
  map<A, B>(fa: F & { value: A }, fn: (a: A) => B): F & { value: B }
}

Or libraries like fp-ts emulate HKT with encoding tricks.

🎯 One-Liner

“TypeScript lacks HKTs, but libraries like fp-ts emulate them with encoding patterns.”

9. 🔗 Generics in React

✅ Typing useState

const [val, setVal] = useState<string | null>(null);

✅ Typing useReducer

type Action = { type: "inc" } | { type: "dec" };
function reducer(s: number, a: Action): number {
  return a.type === "inc" ? s+1 : s-1;
}
const [count, dispatch] = useReducer(reducer, 0);

✅ Typing Props

interface ButtonProps<T extends "button" | "a"> {
  as: T;
  props: T extends "a" ? { href: string } : { onClick: () => void };
}

🎯 One-Liner

“Generics in React type hooks, props, reducers, and flexible components (e.g., polymorphic components).”

🟦 Type Inference & Compatibility

1. 🔍 Type Inference Basics

Definition:
TypeScript infers types when not explicitly annotated.

✅ Examples

let x = 42;      // inferred as number
let y = [1, 2];  // inferred as number[]

✅ Contextual Typing

  • Function parameters infer type from usage:
window.onmousedown = (e) => { 
  console.log(e.button); // e inferred as MouseEvent
};

🎯 One-Liner

“Type inference works both from initializer values and from context (like callbacks).”

2. ⚖️ Excess Property Checks

Definition:
TypeScript enforces stricter checks for object literals.

✅ Example

type User = { name: string };
const u1: User = { name: "Alice", age: 30 }; // ❌ excess property
const tmp = { name: "Alice", age: 30 };
const u2: User = tmp; // ✅ allowed (assignment, not literal)

⚠️ Gotchas

  • Literal checks prevent typos in inline objects.
  • Assigning via variable bypasses check.

🎯 One-Liner

“Object literals get extra checks for unknown properties. Assign via variable to bypass.”

3. 🔄 Type Widening & Narrowing

✅ Widening

  • Without as const, literal types widen:
let a = "hi";     // type string (widened)
const b = "hi";   // type "hi" (literal)

✅ Narrowing

  • Control-flow analysis narrows union types:
function f(x: string | null) {
  if (x !== null) return x.toUpperCase(); // narrowed to string
}

⚠️ Gotchas

  • let x = null → type is any unless strictNullChecks.
  • Arrays widen unless frozen with as const.

🎯 One-Liner

“TS widens literals by default. Use as const to keep narrow literal types.”

4. 🏷️ Assignability & Compatibility

Definition:
TypeScript is structurally typed → assignability depends on shape.

✅ Example

type Point = { x: number; y: number };
type Coord = { x: number; y: number; z?: number };

let p: Point = { x: 1, y: 2 };
let c: Coord = p;  // ✅ works
p = c;             // ✅ works (z optional)

⚠️ Gotchas

  • Function parameters are bivariant by default (unsafe).
  • Use strictFunctionTypes for true contravariance.

🎯 One-Liner

“TS uses structural typing: if shapes match, types are compatible. Functions default to bivariant params unless strict.”

5. 🧩 any vs unknown vs never

✅ any

  • Opt-out of type checking.
  • Can be assigned to/from anything.
let a: any = 42;
a.foo.bar(); // compiles, runtime error

✅ unknown

  • Top type (safe any).
  • Must be narrowed before use.
let b: unknown = 42;
b.toUpperCase(); // ❌ error
if (typeof b === "string") b.toUpperCase(); // ✅

✅ never

  • Bottom type (no value possible).
  • Used in exhaustiveness checks.
type Shape = { kind: "circle" } | { kind: "square" };
function area(s: Shape): number {
  switch (s.kind) {
    case "circle": return 1;
    case "square": return 2;
    default: const _exhaustive: never = s; // ✅ ensures all cases handled
  }
}

🎯 One-Liner

any disables safety, unknown forces narrowing, never represents impossible cases.”

6. 🧠 Inference in Functions

✅ Return Inference

function add(a: number, b: number) {
  return a + b; // inferred as number
}

✅ Generic Inference

function first<T>(arr: T[]): T {
  return arr[0];
}
const v = first(["a", "b"]); // v: string

⚠️ Gotchas

  • Sometimes inference is too wide:
function f() { return Math.random() ? "a" : "b"; }
// inferred as string, not "a"|"b"

→ Fix with as const or explicit typing.

🎯 One-Liner

“Function return types are inferred, but unions may widen. Use as const or annotations for precision.”

7. 🛠️ Type Compatibility in Enums

✅ Numeric Enums

  • Compatible with numbers.
enum Dir { Up, Down }
let d: Dir = 0; // ✅

✅ String Enums

  • Not compatible with strings unless explicitly.
enum Color { Red = "red" }
let c: Color = "red"; // ❌

🎯 One-Liner

“Numeric enums are assignable to numbers, but string enums require exact matches.”

8. ⚡ Type Compatibility in Tuples vs Arrays

✅ Tuples

type Pair = [string, number];
let p: Pair = ["hi", 42];
  • Tuples have fixed length, arrays are flexible.

⚠️ Gotchas

let t: [number, number] = [1, 2];
t.push(3); // ✅ allowed! TS doesn’t enforce length at runtime

🎯 One-Liner

“Tuples enforce order but not length at runtime — they’re arrays under the hood.”

🟦 Type Safety in JavaScript Interop

1. 📜 Ambient Declarations (declare)

Definition:
Tell TypeScript about variables/modules that exist at runtime (JS), but aren’t defined in TS code.

✅ Examples

declare const VERSION: string;
console.log(VERSION); // TS knows VERSION is string

declare module "legacy-lib" {
  export function legacyFn(): void;
}

⚠️ Gotchas

  • Declarations don’t generate runtime code — only inform compiler.
  • If declaration doesn’t match actual runtime → runtime errors.

🎯 One-Liner

declare informs the type system about external JS values but doesn’t emit code. Accuracy is critical or you’ll get runtime errors.”

2. 📦 Type Declarations for JS Libraries

✅ Strategies

  • @types packages:
npm install --save-dev @types/lodash
  • Manual d.ts files for missing types:
// lodash.d.ts
declare module "lodash" {
  export function chunk<T>(arr: T[], size: number): T[][];
}

⚠️ Gotchas

  • If types are outdated/mismatched → TS lies about API.
  • Can “augment” instead of redefining (see below).

🎯 One-Liner

“Missing types for JS libs? Install @types or write .d.ts files — but keep them accurate with the runtime.”

3. 🧩 Module Augmentation & Declaration Merging

Definition:
Extend existing module or type definitions without rewriting.

✅ Example

// add a method to lodash
declare module "lodash" {
  export function customHello(): string;
}

✅ Declaration Merging

  • Interfaces with the same name merge:
interface User { id: number }
interface User { name: string }
const u: User = { id: 1, name: "Alice" }; // ✅

⚠️ Gotchas

  • Augmentation is global → can cause conflicts across packages.
  • Prefer module augmentation for libs, not any hacks.

🎯 One-Liner

“Declaration merging/augmentation extends types safely. Useful for adding custom fields or extending 3rd-party libraries.”

4. 🎯 as const for Safe Interop

Definition:
as const freezes literals into readonly narrow types.

✅ Example

const roles = ["admin", "user", "guest"] as const;
type Role = typeof roles[number]; 
// "admin" | "user" | "guest"

✅ Use Case

  • Ensures config/constants match at runtime.
  • Great for enums/union types from JS arrays.

🎯 One-Liner

as const locks JS literals into readonly narrow types — perfect for role lists, config, or enum-like structures.”

5. ⚙️ tsconfig Strictness Flags

✅ Important Flags

  • strict → enables all strict checks.
  • noImplicitAny → no silent any.
  • strictNullChecks → enforce explicit nullability.
  • noUncheckedIndexedAccess → array lookups can return undefined.
  • exactOptionalPropertyTypes? means strictly optional.

⚠️ Gotchas

  • Teams often disable strictness → leaks any.
  • Migrating large JS → TS requires incremental adoption.

🎯 One-Liner

“Always enable strict. Key flags like noImplicitAny and strictNullChecks catch hidden bugs in JS interop.”

6. 🛠️ Working with Untyped JS Objects

✅ Options

  • Use unknown + type guards:
function isUser(u: any): u is { id: number } {
  return typeof u.id === "number";
}
  • Or validate at runtime with Zod/io-ts:
import { z } from "zod";
const User = z.object({ id: z.number(), name: z.string() });
type User = z.infer<typeof User>;

🎯 One-Liner

“Untyped JS objects should be validated at runtime with schemas (Zod/io-ts), not just trusted via any.”

7. 🔗 Migrating JS → TS (Gradual Typing)

✅ Strategies

  • Rename .js.ts or .tsx.
  • Use allowJs + checkJs in tsconfig to gradually type JS.
  • Add JSDoc annotations:
/**
 * @param {string} name
 * @returns {string}
 */
function greet(name) { return "Hello " + name; }
  • TS infers types from JSDoc until converted.

⚠️ Gotchas

  • JSDoc typing is weaker than TS proper.
  • Incremental migration often mixes any → must clean up later.

🎯 One-Liner

“Migrate JS → TS gradually: enable checkJs, add JSDoc types, then refactor into full TypeScript.”

8. 🛡️ Runtime vs Compile-Time Safety

Core Rule:
Types vanish at runtime → if interoping with JS, you need runtime checks.

✅ Example

function safeParse(json: string): unknown {
  try { return JSON.parse(json) } catch { return null }
}
const data = safeParse("not-json"); // type unknown
// must validate before using

🎯 One-Liner

“TypeScript only checks at compile time. For JS interop, add runtime validation to truly guarantee safety.”

🟦 Type Guards & Narrowing

1. 🔎 Built-in Type Guards: typeof

Definition:
typeof lets TypeScript narrow primitive types.

✅ Example

function log(val: string | number) {
  if (typeof val === "string") {
    console.log(val.toUpperCase()); // val: string
  } else {
    console.log(val.toFixed(2));    // val: number
  }
}

⚠️ Gotchas

  • Only works on primitives: "string" | "number" | "boolean" | "bigint" | "symbol" | "undefined" | "object" | "function".
  • Doesn’t differentiate null vs object (typeof null === "object").

🎯 One-Liner

“Use typeof for primitives — but note typeof null === 'object'.”

2. 🏗️ Built-in Type Guards: instanceof

Definition:
Narrow objects by checking prototype chain.

✅ Example

function handleError(e: Error | string) {
  if (e instanceof Error) {
    console.error(e.message); // Error
  } else {
    console.error(e); // string
  }
}

⚠️ Gotchas

  • Only works with classes/constructors (not plain objects).
  • Inheritance hierarchy is respected.

🎯 One-Liner

“Use instanceof for class-based narrowing — it checks prototype chain.”

3. 🧩 In-Operator Narrowing

Definition:
Check if a property exists in object → narrows union.

✅ Example

type Cat = { meow: () => void };
type Dog = { bark: () => void };

function speak(animal: Cat | Dog) {
  if ("meow" in animal) animal.meow();
  else animal.bark();
}

🎯 One-Liner

“The in operator narrows unions by checking for property existence.”

4. 🏷️ Discriminated (Tagged) Unions

Definition:
Unions with a common literal field (“tag”) for safe narrowing.

✅ Example

type Shape =
  | { kind: "circle"; radius: number }
  | { kind: "square"; side: number };

function area(s: Shape) {
  switch (s.kind) {
    case "circle": return Math.PI * s.radius ** 2;
    case "square": return s.side ** 2;
  }
}

🎯 One-Liner

“Discriminated unions use a common literal field to guarantee safe narrowing in switches.”

5. 🛠️ Custom Type Predicates

Definition:
User-defined functions that tell TS a condition implies a type.

✅ Example

function isString(x: unknown): x is string {
  return typeof x === "string";
}

function log(x: unknown) {
  if (isString(x)) console.log(x.toUpperCase());
}

🎯 One-Liner

“Custom type predicates (x is T) let you teach TS how to narrow beyond built-ins.”

6. 🚦 Exhaustiveness Checking with never

Definition:
Force handling all cases in a union.

✅ Example

type Shape =
  | { kind: "circle"; r: number }
  | { kind: "square"; s: number };

function perimeter(s: Shape) {
  switch (s.kind) {
    case "circle": return 2 * Math.PI * s.r;
    case "square": return 4 * s.s;
    default:
      const _exhaustive: never = s; // compile error if new case added
      return _exhaustive;
  }
}

🎯 One-Liner

“Exhaustiveness checks with never ensure all union cases are handled — future-proofing code.”

7. 🧠 Control Flow Analysis

Definition:
TS tracks variables through branches to narrow automatically.

✅ Example

function f(x: string | null) {
  if (!x) return; 
  // x is now string, since null was filtered out
  return x.toUpperCase();
}

⚠️ Gotchas

  • TS is flow-sensitive — order matters.
  • Reassignments can widen again.

🎯 One-Liner

“TS narrows types flow-sensitively — once a check passes, TS refines type until reassignment.”

8. 🧩 Assertion Functions (TS 3.7+)

Definition:
Custom functions that throw on invalid values while narrowing type.

✅ Example

function assertIsString(x: any): asserts x is string {
  if (typeof x !== "string") throw new Error("Not a string");
}

function shout(x: any) {
  assertIsString(x);
  console.log(x.toUpperCase()); // x: string
}

🎯 One-Liner

“Assertion functions throw at runtime and tell TS the variable is narrowed if no error occurs.”

9. 📦 Combining Guards

✅ Example

function handle(input: string | number | null) {
  if (input == null) return;          // null/undefined filtered
  if (typeof input === "string") {    // narrowed to string
    return input.toUpperCase();
  }
  return input.toFixed(2);            // number
}

🎯 One-Liner

“Combine guards (== null, typeof, instanceof) for precise narrowing of complex unions.”

🟦 Advanced Patterns

1. 🏷️ Branded Types (Nominal Typing in TS)

Problem:
TS is structuraltype UserId = string is indistinguishable from any string.

Solution:
Add a “brand” field to enforce nominal typing.

✅ Example

type UserId = string & { __brand: "UserId" };
function getUser(id: UserId) {}
getUser("123" as UserId);  // ✅
getUser("random");         // ❌ must be branded

🎯 One-Liner

“Branded types simulate nominal typing in TS, preventing accidental mixing of structurally identical types.”

2. 🌀 Opaque Types

Definition:
Similar to branded types, but completely hide the underlying type from consumers.

✅ Example

type Opaque<K, T> = T & { __TYPE__: K };
type UserId = Opaque<"UserId", string>;

function createUserId(s: string): UserId {
  return s as UserId;
}

🎯 One-Liner

“Opaque types hide implementation details and prevent misuse, forcing controlled constructors.”

3. 🛠️ Recursive Utility Types

✅ DeepPartial

type DeepPartial<T> = {
  [K in keyof T]?: DeepPartial<T[K]>;
};

✅ DeepReadonly

type DeepReadonly<T> = {
  readonly [K in keyof T]: DeepReadonly<T[K]>;
};

⚠️ Gotchas

  • Deep recursion can slow down compiler.
  • Use selectively in large projects.

🎯 One-Liner

“Recursive types enable deep utilities like DeepPartial, but heavy use impacts compiler performance.”

4. 🧩 Conditional & Mapped Utilities

✅ NonNullable

type NonNullable<T> = T extends null | undefined ? never : T;

✅ Diff

type Diff<T, U> = T extends U ? never : T;

✅ Overwrite

type Overwrite<T, U> = Omit<T, keyof U> & U;

🎯 One-Liner

“Mapped + conditional types let you build powerful utilities like Diff, Overwrite, NonNullable.”

5. 🔄 Variadic Tuple Types

Definition:
Model tuples of variable length with generics.

✅ Example

type Push<T extends any[], V> = [...T, V];
type T1 = Push<[1,2], 3>; // [1,2,3]

type Concat<T extends any[], U extends any[]> = [...T, ...U];
type T2 = Concat<[1,2], [3,4]>; // [1,2,3,4]

🎯 One-Liner

“Variadic tuple types let you append, prepend, or merge tuples while preserving type precision.”

6. 🏗️ Builder Pattern in TypeScript

✅ Example

class RequestBuilder {
  private url: string = "";
  private method: "GET" | "POST" = "GET";

  setUrl(url: string) { this.url = url; return this; }
  setMethod(m: "GET" | "POST") { this.method = m; return this; }
  build() { return { url: this.url, method: this.method }; }
}

const req = new RequestBuilder().setUrl("/api").setMethod("POST").build();

🎯 One-Liner

“Builder patterns ensure chained configuration APIs with type safety and autocomplete.”

7. 🛡️ Exact Types (Prevent Excess Keys)

Problem:
TS normally allows extra props via assignment.

Solution:
Create an Exact<T, U> utility.

✅ Example

type Exact<T, U extends T> = T & { [K in Exclude<keyof U, keyof T>]?: never };

type Person = { name: string };
const p: Exact<Person, { name: string; age: number }> = { name: "A", age: 20 }; 
// ❌ error: extra 'age'

🎯 One-Liner

“Exact types prevent excess keys, useful for APIs where only known fields are allowed.”

8. 📦 Extracting Types from Values

✅ typeof + keyof

const config = {
  roles: ["admin", "user", "guest"] as const,
};
type Role = typeof config["roles"][number]; 
// "admin" | "user" | "guest"

🎯 One-Liner

“Use typeof and as const to derive union types from runtime values like config arrays.”

9. 🧠 Phantom Types (Static Guarantees)

Definition:
Types that exist only at compile-time, to encode invariants.

✅ Example

type Celsius = number & { __unit: "Celsius" };
type Fahrenheit = number & { __unit: "Fahrenheit" };

function toF(c: Celsius): Fahrenheit { return (c * 9/5 + 32) as Fahrenheit; }

let t: Celsius = 100 as Celsius;
toF(t); // ✅
toF(100 as Fahrenheit); // ❌

🎯 One-Liner

“Phantom types enforce domain-specific rules (like units) without runtime cost.”

🟦 TypeScript in React

1. 🎯 Typing Component Props

✅ Functional Components

type ButtonProps = {
  label: string;
  onClick?: () => void;
};

const Button: React.FC<ButtonProps> = ({ label, onClick }) => (
  <button onClick={onClick}>{label}</button>
);

⚠️ Gotchas

  • React.FC implicitly adds children prop — often unwanted.
  • Better to type children explicitly.
type Props = { children?: React.ReactNode };

🎯 One-Liner

“Prefer explicit prop typing over React.FC to avoid hidden children.”

2. 🏷️ Children Typing

✅ Common Patterns

type Props = { children: React.ReactNode }; // anything renderable
type Props2 = { children: React.ReactElement }; // exactly one element
type Props3<T> = { children: (data: T) => React.ReactNode }; // render prop

🎯 One-Liner

“Use ReactNode for generic children, ReactElement for single elements, and functions for render props.”

3. 🧩 Typing Hooks (useState, useReducer)

✅ useState

const [count, setCount] = useState<number>(0); // explicit
const [name, setName] = useState("Alice"); // inferred as string
  • useState<T | null>(null) when initial value is null.

✅ useReducer

type Action = { type: "inc" } | { type: "dec" };
function reducer(state: number, action: Action): number {
  switch (action.type) {
    case "inc": return state + 1;
    case "dec": return state - 1;
  }
}
const [count, dispatch] = useReducer(reducer, 0);

🎯 One-Liner

“Type state & actions explicitly in hooks. Use union types for reducers.”

4. 🌐 Typing Context Providers

✅ Example

type User = { id: string; name: string };
type UserContextType = { user: User | null; setUser: (u: User) => void };

const UserContext = React.createContext<UserContextType | undefined>(undefined);

function useUser() {
  const ctx = React.useContext(UserContext);
  if (!ctx) throw new Error("useUser must be inside UserProvider");
  return ctx;
}

🎯 One-Liner

“Context values should include both data and setters, wrapped in a custom hook for safety.”

5. 🧵 Typing Refs & forwardRef

✅ DOM Refs

const inputRef = React.useRef<HTMLInputElement>(null);
inputRef.current?.focus();

✅ forwardRef

type InputProps = { label: string };
const Input = React.forwardRef<HTMLInputElement, InputProps>(
  ({ label }, ref) => <input ref={ref} placeholder={label} />
);

✅ useImperativeHandle

type Handle = { focus: () => void };
const CustomInput = React.forwardRef<Handle>((props, ref) => {
  const inputRef = React.useRef<HTMLInputElement>(null);
  React.useImperativeHandle(ref, () => ({
    focus: () => inputRef.current?.focus(),
  }));
  return <input ref={inputRef} />;
});

🎯 One-Liner

“Use forwardRef with generic ref types. Expose imperative APIs with useImperativeHandle.”

6. 🧮 Typing Higher-Order Components (HOCs)

✅ Example

function withLoading<T>(Component: React.ComponentType<T>) {
  return (props: T & { loading: boolean }) =>
    props.loading ? <div>Loading...</div> : <Component {...props} />;
}

⚠️ Gotchas

  • Must preserve props (T) and merge with new ones.
  • Watch out for lost generics when wrapping.

🎯 One-Liner

“HOCs should preserve original props via generics and merge additional ones.”

7. 🧑‍🔬 Typing Generic Components

✅ Example

type ListProps<T> = {
  items: T[];
  render: (item: T) => React.ReactNode;
};

function List<T>({ items, render }: ListProps<T>) {
  return <ul>{items.map(render)}</ul>;
}

<List items={[1, 2, 3]} render={(x) => <li>{x}</li>} />;

🎯 One-Liner

“Generic components let props depend on type parameters — perfect for reusable lists and tables.”

8. 🔄 Typing Event Handlers

✅ Example

function Form() {
  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    console.log(e.target.value);
  };
  return <input onChange={handleChange} />;
}

✅ Common Event Types

  • React.MouseEvent<HTMLButtonElement>
  • React.ChangeEvent<HTMLInputElement>
  • React.FormEvent<HTMLFormElement>

🎯 One-Liner

“Use React’s synthetic event types (MouseEvent, ChangeEvent) for handlers, parameterized by element type.”

9. 🧩 Typing Polymorphic as Components

✅ Example

type PolymorphicProps<T extends React.ElementType> = {
  as?: T;
  children: React.ReactNode;
} & React.ComponentProps<T>;

function Box<T extends React.ElementType = "div">({ as, ...props }: PolymorphicProps<T>) {
  const Component = as || "div";
  return <Component {...props} />;
}

<Box as="a" href="https://ts">Link</Box>; // href type-safe

🎯 One-Liner

“Polymorphic components use as + generics + ComponentProps<T> to forward correct props.”

🟦 Performance & Scaling TypeScript

1. ⚡ Type-Checking Performance in Large Projects

✅ Common Bottlenecks

  • Deeply nested conditional types.
  • Overuse of recursive mapped types (e.g., DeepPartial, DeepReadonly).
  • Giant union types (e.g., "A" | "B" | ... | "Z" with hundreds of members).
  • Heavy infer usage inside generics.

✅ Tools

  • tsc --diagnostics → measure type-check performance.
  • tsc --extendedDiagnostics → detailed breakdown (parse time, check time, emit time).

🎯 One-Liner

“The biggest type-check killers are deep recursion, massive unions, and heavy conditional types — profile with --diagnostics.”

2. 🛠️ Avoiding Over-Complex Types

✅ Problem

Some teams abuse TS to encode too much at type level.

type Crazy<T> = T extends string 
  ? { str: T } 
  : T extends number 
    ? { num: T } 
    : never;
  • Hard to maintain, slows compiler.

✅ Guidelines

  • Keep types simple for DX (developer experience).
  • Don’t encode logic that belongs in runtime code.
  • Prefer branded/opaque types for safety, instead of extreme conditional gymnastics.

🎯 One-Liner

“Don’t over-engineer types — TS is for safety, not replacing runtime logic.”

3. 📦 Build Pipelines: tsc vs Babel vs SWC

✅ tsc

  • Full type-checker + emit.
  • Slowest, but canonical.

✅ Babel with @babel/preset-typescript

  • Strips types → no type-checking.
  • Fast, but must run tsc --noEmit separately.

✅ SWC (used in Next.js, Vite, Turborepo)

  • Rust-based transpiler, very fast.
  • Strips types only.

⚠️ Gotchas

  • Babel/SWC do not catch type errors — must run type-check separately in CI.

🎯 One-Liner

“Use SWC/Babel for fast builds, but keep tsc --noEmit in CI for type safety.”

4. 🔄 Incremental Compilation

✅ Options

  • "incremental": true in tsconfig.json → saves .tsbuildinfo cache.
  • "composite": true for project references (multi-package repos).

✅ Benefits

  • Only re-check changed files.
  • Required for monorepos with shared libraries.

🎯 One-Liner

“Enable incremental + composite in tsconfig to avoid full re-checks in large repos.”

5. 🧩 Project References (Scaling Monorepos)

Definition:
Break project into multiple sub-projects with clear build boundaries.

✅ Example

// tsconfig.json
{
  "files": [],
  "references": [
    { "path": "./packages/ui" },
    { "path": "./packages/server" }
  ]
}

✅ Benefits

  • Faster builds (independent packages).
  • Enforces dependency contracts.
  • Works great with Nx/Turborepo.

🎯 One-Liner

“Project references let you split large codebases into smaller typed units with enforced contracts.”

6. 🧠 Type-Only Imports & Exports (TS 3.8+)

✅ Example

import type { User } from "./types"; // stripped at runtime
export type { Config } from "./config";

✅ Benefits

  • Avoids accidentally bundling type-only modules.
  • Reduces unnecessary runtime imports.

🎯 One-Liner

“Use import type and export type to ensure pure type imports don’t affect runtime bundles.”

7. ⚖️ Managing any at Scale

✅ Problems

  • any spreads like a virus in codebases.
  • One any can propagate through dozens of types.

✅ Solutions

  • Use unknown instead of any when possible.
  • Use eslint rules (@typescript-eslint/no-explicit-any).
  • Introduce “escape hatches” (TODO: fix any) but track debt.

🎯 One-Liner

“Manage any aggressively — prefer unknown, enforce lint rules, and track escape hatches.”

8. 📊 Large Codebase Best Practices

  • ✅ Strict mode always (strict: true).
  • ✅ Type-only imports (import type).
  • ✅ Use Zod/io-ts for runtime validation of API responses.
  • ✅ Add tsc --noEmit in CI to enforce type safety.
  • ✅ Use paths in tsconfig.json for clean imports.
  • ✅ Monitor tsc --diagnostics to spot type-check slowdowns.

🎯 One-Liner

“Large TS projects succeed when strict mode, type-only imports, runtime validation, and CI type-checks are enforced.”

🟦 Ecosystem & Future

1. 🧩 Decorators (Stage 3 Proposal)

Definition:
Annotations for classes, methods, and properties.

✅ Example

function readonly(target: any, key: string) {
  Object.defineProperty(target, key, { writable: false });
}

class User {
  @readonly
  name = "Alice";
}

✅ Use Cases

  • Dependency injection (NestJS).
  • ORMs (TypeORM, Prisma).
  • Metadata reflection.

⚠️ Gotchas

  • Still experimental — syntax differs across versions.
  • Requires "experimentalDecorators": true in tsconfig.json.

🎯 One-Liner

“Decorators add metadata to classes/members — useful in frameworks like NestJS, but still experimental in TS.”

2. 📜 Type Annotations in JavaScript (TC39 Proposal)

Definition:
JavaScript itself may gain type syntax (stripped at runtime).

✅ Example (future JS)

function add(a: number, b: number): number {
  return a + b;
}
  • Types would be ignored at runtime, like TS today.
  • TS would align with native JS type syntax.

🎯 One-Liner

“JS is moving toward built-in type annotations. TS will align, making gradual adoption easier.”

3. ⚖️ TypeScript vs Flow vs Others

✅ TypeScript

  • Mainstream, broad ecosystem.
  • Stronger tooling, VSCode integration.

✅ Flow (Meta)

  • Better type inference in theory.
  • Lost adoption due to ecosystem fragmentation.

✅ Elm / ReasonML / PureScript

  • Stronger type systems, but niche.

🎯 One-Liner

“TS won the ecosystem war — Flow and others have niche uses, but TS dominates frontend and Node.”

4. 🆕 New & Recent TS Features

satisfies Operator (TS 4.9)

const theme = {
  primary: "blue",
  secondary: "red"
} satisfies Record<string, string>;
  • Ensures structure without widening values.

const Type Parameters (coming soon)

function tuple<const T extends string[]>(...args: T): T {
  return args;
}
const t = tuple("a", "b"); // type ["a", "b"]

✅ Variance Annotations (future)

  • Explicitly mark generics as in (contravariant) or out (covariant).

🎯 One-Liner

“Features like satisfies and const generics improve precision without hacks — future TS is about better inference + clarity.”

5. 🏗️ Migration Strategies

✅ JS → TS

  • Enable allowJs + checkJs.
  • Rename .js.ts gradually.
  • Add strict config (noImplicitAny, strictNullChecks).
  • Replace JSDoc with real types.

✅ Flow → TS

  • Use codemods (flow-to-ts).
  • Incrementally replace types.

✅ Legacy TS → Modern

  • Remove namespace in favor of ES modules.
  • Switch to strict mode.
  • Replace /// <reference> with proper imports.

🎯 One-Liner

“Migrate incrementally: JS → TS with checkJs, Flow → TS with codemods, legacy TS → strict modules.”

6. 🏢 TypeScript at Scale

✅ Observations

  • At very large scale (10M+ LOC), TS type-checking can bottleneck.
  • Some companies (Google, Meta) experiment with faster type-checkers (SWC, Rome, incremental builds).
  • Types become API contracts between teams — not just safety.

🎯 One-Liner

“At scale, TypeScript types are contracts between teams. Performance requires project references + incremental builds.”

7. 🔮 Future of TypeScript

✅ Trends

  • Closer alignment with JavaScript (native type annotations).
  • Better inference (const generics, variance).
  • Compiler performance improvements (Rust-based checkers like tsc-swc).
  • More runtime type-checking integration (Zod + TS).

🎯 One-Liner

“The future of TS is tighter JS integration, better inference, and faster compilers — runtime validation will bridge static gaps.”

✅ Summary (Full Handbook)

This TypeScript handbook covers staff/architect-level depth:

  • Fundamentals (type system vs runtime, primitives, assertions, narrowing, null checks, structural typing, interfaces vs types, enums vs literal types)
  • Advanced Types (unions, intersections, literals, const assertions, template literals, conditional, mapped, utility types, recursive types)
  • Generics (functions, constraints, defaults, keyof/indexed access, conditional generics, variance, HKTs, React generics)
  • Type Inference & Compatibility (inference, widening/narrowing, assignability, any vs unknown vs never, enums, tuples vs arrays)
  • JavaScript Interop (ambient declarations, @types, module augmentation, declaration merging, as const, tsconfig strictness, runtime validation, gradual migration)
  • Type Guards & Narrowing (typeof, instanceof, in-operator, discriminated unions, custom predicates, assertion functions, exhaustiveness checks)
  • Advanced Patterns (branded types, opaque types, recursive utilities, mapped utilities, variadic tuples, builder pattern, exact types, phantom types)
  • TypeScript in React (props, children, hooks, context, refs, HOCs, generic components, event handlers, polymorphic components)
  • Performance & Scaling (diagnostics, avoiding complex types, Babel/SWC vs tsc, incremental compilation, project references, import type, managing any, best practices)
  • Ecosystem & Future (decorators, JS type annotations proposal, TS vs Flow, new features like satisfies/const generics, migration strategies, TS at scale, future trends)


This content originally appeared on DEV Community and was authored by Animesh Pandey


Print Share Comment Cite Upload Translate Updates
APA

Animesh Pandey | Sciencx (2025-08-31T19:19:44+00:00) TypeScript Expert Revision Handbook. Retrieved from https://www.scien.cx/2025/08/31/typescript-expert-revision-handbook/

MLA
" » TypeScript Expert Revision Handbook." Animesh Pandey | Sciencx - Sunday August 31, 2025, https://www.scien.cx/2025/08/31/typescript-expert-revision-handbook/
HARVARD
Animesh Pandey | Sciencx Sunday August 31, 2025 » TypeScript Expert Revision Handbook., viewed ,<https://www.scien.cx/2025/08/31/typescript-expert-revision-handbook/>
VANCOUVER
Animesh Pandey | Sciencx - » TypeScript Expert Revision Handbook. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2025/08/31/typescript-expert-revision-handbook/
CHICAGO
" » TypeScript Expert Revision Handbook." Animesh Pandey | Sciencx - Accessed . https://www.scien.cx/2025/08/31/typescript-expert-revision-handbook/
IEEE
" » TypeScript Expert Revision Handbook." Animesh Pandey | Sciencx [Online]. Available: https://www.scien.cx/2025/08/31/typescript-expert-revision-handbook/. [Accessed: ]
rf:citation
» TypeScript Expert Revision Handbook | Animesh Pandey | Sciencx | https://www.scien.cx/2025/08/31/typescript-expert-revision-handbook/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.