TypeScript & Type System Mechanics
Master TypeScript from core type annotations to generic constraints, conditional infer types, utility functions, and compiler options.

Master TypeScript from core type annotations to generic constraints, conditional infer types, utility functions, and compiler options.
TypeScript Primitives & Explicit Type Annotations
TypeScript erases entirely at build time, so a `string` annotation produces no runtime check whatsoever. A value arriving from an API is whatever the server actually sent, regardless of what the annotation claims.
Type Inference & Literal Types (const vs. let)
`let x = "GET"` infers `string` because the binding can change; `const x = "GET"` infers the literal `"GET"`. That widening rule is why object properties need `as const` to stay narrow.
Arrays, Tuples & Readonly Modifiers
`string[]` is any number of strings; `[string, number]` is exactly two, in that order. `readonly` removes the mutating methods at compile time, so `push` becomes a type error rather than a runtime surprise.
Objects & Type Aliases (type Keyword)
`?` makes a property optional, which adds `undefined` to its type rather than removing the key. That is why reading it still needs a check even after the shape is declared.
Interfaces vs. Type Aliases: When to Use Which
Two `interface` declarations with the same name merge; two `type` aliases collide. That single behaviour decides the choice: merging is what makes interfaces right for public APIs others need to extend.
Union Types (|) & Type Narrowing
A union permits only what every member allows, so shared properties are all you can touch. `typeof`, `instanceof` and `in` narrow the union inside a branch, unlocking the rest of that member's shape.
Optional Properties & strictNullChecks
Without `strictNullChecks`, `null` is assignable to every type and the compiler hides the entire class of null bugs. Turning it on is what makes `?.` and `??` meaningful rather than decorative.
Function Type Signatures & Function Overloads
Overload signatures describe the callable shapes; the `implementation` signature sits beneath them and is not callable itself. Only the overloads are visible to callers, which is why the implementation can be loosely typed.
Enums vs. Const Assertions (as const)
A numeric `enum` emits a real object and a reverse mapping into the bundle. `as const` produces the same literal union with zero runtime output, which is why it is now the usual choice.
Literal Types & Template Literal Types
A literal type accepts exactly one value, so `"GET"` is narrower than `string`. Template literal types compose them, letting `` `on${Capitalize<K>}` `` generate every handler name from an existing type.
Type Assertions (as) & Non-Null Operator (!)
`as` and `!` silence the compiler; they check nothing at runtime. Every assertion is a claim you are now responsible for, which is why a type guard is preferable wherever the check can actually be made.
The unknown Type vs. any (Type Safety First)
`any` switches the compiler off for that value; `unknown` accepts anything but permits nothing until it is narrowed. That is why `unknown` is the correct type for a parsed JSON response.
The never Type & Exhaustiveness Checking
`never` is the type with no possible value, so nothing is assignable to it. Assigning the narrowed variable to `never` in a default branch turns a forgotten union member into a compile error.
Index Signatures & Mapped Types
An index signature types keys that are not known in advance; a mapped type builds new keys from ones that are. `[K in keyof T]` is what makes `Partial` and `Readonly` ordinary library code rather than compiler magic.
Introduction to Generics (<T>)
A generic passes the caller's type through the function instead of erasing it, so `identity(5)` returns `number` rather than `any`. That is the difference between reusable and untyped.
Generic Constraints (T extends Target)
An unconstrained `T` could be anything, so the compiler blocks every property access on it. `T extends { id: string }` promises the shape, restoring access while still accepting any type that satisfies it.
Discriminated Unions & Pattern Matching
Give every member of a union the same literal tag field and a check on that tag narrows the whole object. That is what lets `state.error` be a type error unless the tag says the state actually has one.
Built-in Utility Types: Partial, Pick & Omit
`Partial<T>` makes every property optional, `Pick` keeps named keys and `Omit` drops them. Deriving these from one source type is what stops an update payload drifting from the entity it updates.
Utility Types: Record, Extract & ReturnType
`Record<K, V>` builds an object type from a key union, and `ReturnType<F>` reads a function's result type back out. Both derive from existing declarations, so the types cannot fall out of step with the code.
The keyof & typeof Type Operators
`typeof` lifts a runtime value into the type world, and `keyof` turns a type into the union of its keys. Chained as `keyof typeof config` they derive the valid key list from the object itself.
Conditional Types & The infer Keyword
A conditional type is a ternary the compiler evaluates, choosing one type when `T extends U` and another otherwise. `infer` captures part of the matched shape into a new variable, which is how `ReturnType` extracts a function's result.
Deep Readonly & Recursive Type Aliases
A type alias may reference itself, which is what allows a tree node or a JSON value to be described at all. Mapping over the keys recursively is how `readonly` reaches every level instead of only the first.
Classes, Access Modifiers & implements
`private` is erased at compile time; the field is still reachable from plain JavaScript at runtime. `#name` is the version the engine actually enforces, which is the difference worth knowing.
Abstract Classes & Polymorphism
An `abstract` class cannot be instantiated, and its abstract methods have no body because every subclass must supply them. That is how a base class fixes an algorithm's shape while leaving each step open.
Declaration Files (.d.ts) & Ambient Modules
A `.d.ts` file describes types with no implementation, which is how JavaScript packages get typed without being rewritten. `@types/*` packages are exactly that, published separately from the library.
The satisfies Operator (TypeScript 4.9+)
`as` overrides the compiler, and `:` widens the value to the annotation. `satisfies` checks against the type while keeping the narrow inferred literals, which is why it validates config without flattening it.
Branded Types & Nominal Typing
TypeScript compares types by shape, so a `UserId` and a raw `string` are interchangeable to the compiler. A brand adds a phantom field that exists only at compile time, making the two genuinely incompatible.
Strict Compiler Flags in tsconfig.json
`strict` is a bundle, not a single flag: it turns on `noImplicitAny`, `strictNullChecks` and six others at once. `noUncheckedIndexedAccess` sits outside it, making `arr[0]` admit it may be `undefined`.
TypeScript Decorators & Metadata (TS 5.0+)
A decorator is a function that receives the thing it decorates and returns a replacement, so it wraps behaviour rather than editing it. TypeScript 5.0 switched to the Stage 3 standard, and the signatures changed.
TypeScript Project References & Monorepos
Project references let each package build and cache independently, so touching one does not recompile the monorepo. `composite` emits the `.d.ts` files the other projects consume in place of source.