๐ 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. Preferunknown
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 ofnew 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 isany
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 silentany
. -
strictNullChecks
โ enforce explicit nullability. -
noUncheckedIndexedAccess
โ array lookups can returnundefined
. -
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 likenoImplicitAny
andstrictNullChecks
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
vsobject
(typeof null === "object"
).
๐ฏ One-Liner
โUse
typeof
for primitives โ but notetypeof 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 structural โ type 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
andas 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 addschildren
prop โ often unwanted. - Better to type
children
explicitly.
type Props = { children?: React.ReactNode };
๐ฏ One-Liner
โPrefer explicit prop typing over
React.FC
to avoid hiddenchildren
.โ
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 withuseImperativeHandle
.โ
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
intsconfig.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
andexport 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 ofany
when possible. - Use
eslint
rules (@typescript-eslint/no-explicit-any
). - Introduce โescape hatchesโ (
TODO: fix any
) but track debt.
๐ฏ One-Liner
โManage
any
aggressively โ preferunknown
, 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
intsconfig.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
intsconfig.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) orout
(covariant).
๐ฏ One-Liner
โFeatures like
satisfies
andconst
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)
Top comments (0)