Blog

Blog · Complete Guides

The Complete Guide to TypeScript in 2026

A complete guide to TypeScript — types, interfaces vs type aliases, unions and narrowing, generics, utility types, the satisfies operator, tsconfig and modern features — with examples and current (2026) context.

XCODX Team · 6 min read

TypeScript is JavaScript with static types — it catches bugs before you run the code, powers better autocomplete, and is effectively the industry default for professional web development in 2026. This complete guide covers the type system from basics to generics and utility types, plus config and the modern features worth knowing.

Getting started

Install TypeScript, initialize a config, and compile. In practice most projects run TS through a bundler (Vite) or a runtime that strips types, but tsc is the reference compiler and type-checker.

npm install -D typescript
npx tsc --init         # creates tsconfig.json
npx tsc                # type-check + compile
npx tsc --noEmit       # type-check only

Basic types

You annotate variables, parameters and return values with types. TypeScript also *infers* types, so you often don’t need to write them — annotate function signatures and let inference handle the rest.

let name: string = "Ada";
let age: number = 36;
let active: boolean = true;
let tags: string[] = ["a", "b"];
let point: [number, number] = [0, 0]; // tuple

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

// special types
let u: unknown;   // must be narrowed before use
let n: never;     // a value that never occurs
// avoid `any` — it turns off type checking

Interfaces vs type aliases

Both describe the shape of data. Use an interface for object shapes and public contracts (they extend and merge cleanly); use a type alias for unions, intersections, primitives, tuples and anything computed.

interface User {
  id: number;
  name: string;
  email?: string;        // optional
  readonly createdAt: Date;
}

type ID = string | number;              // union — needs a type alias
type Admin = User & { role: "admin" };  // intersection

Unions, literals and narrowing

A union (A | B) means "one of these." Literal types narrow further to exact values. TypeScript then *narrows* a union to a specific type based on your checks — typeof, in, instanceof, equality, or a discriminant field.

type Status = "idle" | "loading" | "error"; // literal union

function area(s: { kind: "circle"; r: number } | { kind: "square"; side: number }) {
  // discriminated union — narrow on `kind`
  if (s.kind === "circle") return Math.PI * s.r ** 2;
  return s.side ** 2;
}

Generics

Generics let you write reusable, type-safe functions and types that work over any type while preserving the relationship between inputs and outputs. The type parameter (<T>) is filled in by the caller or inferred.

function first<T>(arr: T[]): T | undefined {
  return arr[0];
}
first([1, 2, 3]);      // T = number
first(["a", "b"]);     // T = string

interface ApiResponse<T> { data: T; error: string | null; }

Utility types

TypeScript ships built-in generic types that transform other types — you’ll use these constantly instead of hand-writing variants.

  • Partial<T> — all properties optional; Required<T> — all required.
  • Pick<T, K> — a subset of keys; Omit<T, K> — all keys except some.
  • Readonly<T> — all properties read-only.
  • Record<K, V> — an object type with keys K and values V.
  • ReturnType<F> / Parameters<F> — a function’s return type / parameter types.
  • Awaited<T> — the resolved type of a Promise; NonNullable<T> — removes null/undefined.
type UserUpdate = Partial<User>;            // every field optional
type PublicUser = Omit<User, "email">;      // hide email
type UsersById = Record<number, User>;      // { [id]: User }

The satisfies operator

satisfies checks that a value matches a type without widening or changing the inferred type — so you get validation *and* keep the precise literal types for autocomplete. Introduced in TS 4.9, it’s now a staple.

const config = {
  port: 3000,
  host: "localhost",
} satisfies Record<string, string | number>;

config.port; // still typed as number (not string | number)

Enums vs const objects

TypeScript’s enum generates runtime code and doesn’t tree-shake well. In 2026 many teams prefer a const object plus a derived union type — zero runtime overhead and the same ergonomics.

// preferred: const object + union
const Role = { Admin: "admin", User: "user" } as const;
type Role = typeof Role[keyof typeof Role]; // "admin" | "user"

// vs a classic enum (generates runtime code)
enum RoleEnum { Admin = "admin", User = "user" }

Type-checking JavaScript with JSDoc

You don’t need .ts files to get type safety. Add // @ts-check to a .js file (or "checkJs": true in tsconfig) and annotate with JSDoc — great for gradually typing an existing JavaScript codebase.

// @ts-check
/** @param {number} a @param {number} b @returns {number} */
function add(a, b) { return a + b; }
add("1", 2); // ❌ type error, in a plain .js file

Modern features worth knowing

  • satisfies (above) — validate without widening.
  • using / await using (TS 5.2) — explicit resource management; a resource with Symbol.dispose is cleaned up automatically at the end of the scope.
  • const type parameters (TS 5.0) — <const T> infers the most literal type without callers writing as const.
  • noUncheckedIndexedAccess — makes indexed access (arr[i], obj[key]) include undefined, catching a whole class of bugs.
function query(sql: string) {
  using conn = openConnection();  // auto-disposed at scope end
  return conn.run(sql);
}

tsconfig essentials

  • "strict": true — turn on all the strict checks (do this first).
  • "target" / "module" — the JS version and module system to emit.
  • "moduleResolution": "bundler" — for modern bundler-based projects.
  • "esModuleInterop": true — smoother CommonJS/ESM interop.
  • "skipLibCheck": true — skip type-checking of dependencies’ .d.ts (faster).
  • "noUncheckedIndexedAccess": true — safer array/object indexing.

Frequently asked questions

Is TypeScript worth learning in 2026?
Yes — it’s effectively the industry default for professional web development. Most major frameworks and libraries are TypeScript-first, and static types catch a large class of bugs before runtime while making autocomplete and refactoring far better. Learn solid JavaScript first, then add TypeScript so the types clarify your code rather than obscure it.
What is the difference between interface and type in TypeScript?
Both describe shapes. Interfaces are best for object shapes and public contracts — they can be extended and merged, and error messages are often cleaner. Type aliases are more flexible: they can represent unions, intersections, primitives, tuples and computed/mapped types, which interfaces cannot. A common convention is interface for object contracts and type for everything else.
What does the satisfies operator do?
satisfies checks that a value conforms to a type without changing (widening) the value’s inferred type. For example, a config object can be validated against Record<string, string | number> while each property keeps its precise literal type, so autocomplete and narrowing still work. It gives you validation and precision at once, which a plain type annotation would lose.
Should I use enums in TypeScript?
Often a const object plus a derived union type is a better choice than a classic enum. Enums generate runtime JavaScript and don’t tree-shake well, whereas const objects have zero runtime cost and give you the same string/number constants and union types. Enums are still fine, but the as const object pattern is increasingly preferred in 2026.

Write and type-check TypeScript live in XCODX Studio — run it in the browser, no setup. See also JavaScript vs TypeScript, JavaScript best practices and the web development roadmap 2026.