thecodex.expert · The Codex Family of Knowledge
TypeScript

Utility Types and Advanced TypeScript

Partial, Pick, Omit, Record, Exclude — TypeScript's power tools for transforming types without repetition.

TypeScript 5.x Structural typing Last verified:
Canonical Definition

TypeScript's built-in utility types transform existing types without rewriting them — Partial makes all fields optional, Pick selects a subset of fields, Omit removes fields, Record defines key-value maps, and Exclude/Extract manipulate union types — all resolved at compile time with zero runtime cost.

One sentence

TypeScript ships with built-in utility types — Partial, Required, Readonly, Pick, Omit, Record, Exclude, Extract — that transform types without you writing the transformation from scratch every time.

Object utility types

TypeScriptobject_utils.ts
interface User {
    id:      number;
    name:    string;
    email:   string;
    role:    "admin" | "user";
    address?: string;
}

// Partial<T> — all properties become optional
type UserUpdate = Partial<User>;
const update: UserUpdate = { name: "Priya" };   // all other fields omitted

// Required<T> — all optional properties become required
type FullUser = Required<User>;   // address is now required

// Readonly<T> — all properties become readonly
type FrozenUser = Readonly<User>;
// frozenUser.name = "x";   // Error

// Pick<T, K> — keep only specified keys
type UserPreview = Pick<User, "id" | "name">;
// { id: number; name: string }

// Omit<T, K> — remove specified keys
type UserWithoutRole = Omit<User, "role" | "id">;
// { name: string; email: string; address?: string }

// Record<K, V> — object type with keys K and values V
type RolePermissions = Record<"admin" | "user" | "guest", string[]>;
const perms: RolePermissions = {
    admin: ["read","write","delete"],
    user:  ["read","write"],
    guest: ["read"],
};

// Record for a lookup table
type ErrorMessages = Record<number, string>;
const httpErrors: ErrorMessages = { 404: "Not Found", 500: "Server Error" };

Set and function utility types

TypeScriptset_function_utils.ts
// Union manipulation
type AllRoles    = "admin" | "user" | "guest" | "moderator";
type PublicRoles = "user" | "guest";

// Exclude<T, U> — remove U from union T
type PrivateRoles = Exclude<AllRoles, PublicRoles>;  // "admin" | "moderator"

// Extract<T, U> — keep only the members of T that are in U
type CommonRoles = Extract<AllRoles, "user" | "guest" | "unknown">;  // "user" | "guest"

// NonNullable<T> — remove null and undefined
type MaybeString = string | null | undefined;
type DefinitelyString = NonNullable<MaybeString>;  // string

// Function utility types
function fetchUser(id: number, token: string): Promise<User> { ... }

type Params   = Parameters<typeof fetchUser>;       // [number, string]
type Result   = ReturnType<typeof fetchUser>;       // Promise<User>
type Resolved = Awaited<ReturnType<typeof fetchUser>>; // User

// ConstructorParameters<T> — constructor argument types
class Logger {
    constructor(private name: string, private level: number) {}
}
type LoggerArgs = ConstructorParameters<typeof Logger>;  // [string, number]

// InstanceType<T> — the type an constructor produces
type LoggerInstance = InstanceType<typeof Logger>;  // Logger

Advanced patterns: satisfies, const, and template literals

TypeScriptadvanced.ts
// satisfies (TypeScript 4.9) — validate type without widening
const palette = {
    red:   [255, 0, 0],
    green: "#00ff00",
    blue:  [0, 0, 255],
} satisfies Record<string, string | number[]>;

// Without satisfies: palette.red would be string | number[] (widened)
// With satisfies: palette.red is number[] (inferred precisely)
palette.red.map(v => v * 2);   // OK — TypeScript knows it's number[]

// as const — make all values literal types, readonly recursively
const config = {
    host:  "localhost",
    port:  3000,
    debug: true,
} as const;
// config: { readonly host: "localhost"; readonly port: 3000; readonly debug: true }

// Combined with typeof for enums-without-enums
const Direction = {
    NORTH: "north",
    SOUTH: "south",
    EAST:  "east",
    WEST:  "west",
} as const;
type Direction = (typeof Direction)[keyof typeof Direction];
// "north" | "south" | "east" | "west"

// Template literal types for API path validation
type ApiPath = \`/api/\${string}\`;
function callApi(path: ApiPath) { ... }
callApi("/api/users");     // OK
// callApi("/users");     // Error — must start with /api/

tsconfig.json — the critical settings

TypeScripttsconfig.json
{
  "compilerOptions": {
    "target":  "ES2022",       // JS version to output
    "module":  "ESNext",       // module system (ESNext = ES modules)
    "lib":     ["ES2022", "DOM"], // type definitions to include
    "outDir":  "./dist",       // where to put compiled JS
    "rootDir": "./src",        // where source TS lives

    // Strictness — always enable these
    "strict":                      true,  // enables all strict checks below
    "noImplicitAny":               true,  // error on implicit any
    "strictNullChecks":            true,  // null/undefined are not assignable to other types
    "strictFunctionTypes":         true,  // correct function parameter variance
    "noUncheckedIndexedAccess":    true,  // arr[0] is T | undefined, not T
    "noImplicitReturns":           true,  // all code paths must return
    "noFallthroughCasesInSwitch":  true,  // switch cases must break/return

    // Module resolution
    "moduleResolution":  "bundler", // for Vite/esbuild/Webpack 5
    "resolveJsonModule": true,
    "esModuleInterop":   true,
    "isolatedModules":   true,   // each file compilable independently (needed for esbuild)

    // Paths
    "paths": {
      "@/*": ["./src/*"]   // absolute imports like: import { x } from "@/utils"
    }
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}

Declaration merging and module augmentation

TypeScriptmodule_augmentation.ts
// Add properties to an existing module's types
// Useful for adding types to third-party libraries

// Augment Express Request type
import "express";
declare module "express" {
    interface Request {
        user?: { id: number; role: string };
    }
}

// Now req.user is typed everywhere you use Express
app.get("/profile", (req, res) => {
    if (req.user) {
        res.json({ id: req.user.id });   // type-safe
    }
});

// Global augmentation
declare global {
    interface Window {
        analytics: { track(event: string): void };
    }
}
window.analytics.track("page_view");  // type-safe

// Namespace merging — add to a namespace
namespace Validation {
    export interface StringValidator {
        isAcceptable(s: string): boolean;
    }
}
namespace Validation {
    export class LettersOnlyValidator implements StringValidator {
        isAcceptable(s: string) { return /^[A-Za-z]+$/.test(s); }
    }
}
Commonly confused
strict: true in tsconfig is NOT on by default. A fresh TypeScript project without "strict": true has very permissive type checking — implicit any is allowed, null checks are off. Always start with "strict": true. Enabling it on an existing codebase with loose types will produce many errors — this is correct, not a bug.
Partial<T> makes all properties optional, including nested objects. Nested properties are NOT made partial — only the top-level properties are affected. For deep partial, you need a recursive type: type DeepPartial<T> = { [K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K] }.
as const makes values readonly and literal, but does not make objects immutable at runtime. At runtime, the object can still be mutated — as const is a TypeScript compile-time constraint only. It narrows the types to the exact literal values, preventing TypeScript from widening "north" to string.
How this connects
Requires first

The TypeScript type system: soundness and intentional unsoundness

TypeScript is intentionally not fully sound — it makes pragmatic trade-offs. Known unsoundnesses: (1) function parameter bivariance for methods (for compatibility with common OOP patterns); (2) type assertions (as T) bypass the type checker; (3) any is a hole in the type system; (4) object literals have excess property checking but objects in variables do not; (5) noUncheckedIndexedAccess is not in strict and must be enabled separately. The TypeScript team explicitly documents these trade-offs and refers to them as "unsoundness for practicality."

TypeScript 5.x — new features

TypeScript 5.0 (March 2023) introduced: decorator metadata (aligning with the TC39 decorators proposal Stage 3), const type parameters, variadic tuple improvements, and --verbatimModuleSyntax for cleaner ESM output. TypeScript 5.2 added using declarations (using / await using) implementing the TC39 Explicit Resource Management proposal — the TypeScript equivalent of Java try-with-resources, ensuring disposal even on error. TypeScript 5.5 (June 2024) added inferred type predicates — the compiler can automatically infer user-defined type guards from function bodies without requiring explicit value is T annotations.

Sources

1
TypeScript Handbook — Utility Types. typescriptlang.org/docs/handbook/utility-types.html.
2
TypeScript Reference — tsconfig. typescriptlang.org/tsconfig.
3
TypeScript Handbook — Declaration Merging. typescriptlang.org/docs/handbook/declaration-merging.html.
4
TypeScript 5.0 Release Notes. devblogs.microsoft.com/typescript/announcing-typescript-5-0/.
Source confidence: High Last verified: Primary source: TypeScript Handbook · typescriptlang.org/docs/

Sources

1
TypeScript Handbook — Utility Types. typescriptlang.org/docs/handbook/utility-types.html.
2
TypeScript Reference — tsconfig. typescriptlang.org/tsconfig.
3
TypeScript Handbook — Declaration Merging. typescriptlang.org/docs/handbook/declaration-merging.html.
4
TypeScript 5.0 Release Notes. devblogs.microsoft.com/typescript/announcing-typescript-5-0/.