Design.dev design.dev

TypeScript basics

TypeScript is JavaScript with a static type layer erased at compile time. Types describe shapes and constraints so editors and tsc catch mistakes before runtime.

Primitives, arrays, and object types

let n: number = 42;
let s: string = 'hi';
let ok: boolean = true;
let u: undefined = undefined;
let nl: null = null;

const nums: number[] = [1, 2, 3];
const tuple: [string, number] = ['score', 100];

type User = {
  id: string;
  name: string;
  role?: 'admin' | 'member'; // optional
  readonly createdAt: Date;
};

Use readonly for fields that should not be reassigned, and ? for optional properties.

Type inference

TypeScript infers types when initialization makes them obvious.

const count = 10;              // number
const title = 'Guide';         // string
const items = [1, 2, 3];       // number[]

function double(x: number) {
  return x * 2;                // return type inferred as number
}

Add explicit annotations at public APIs, empty arrays you will mutate, and places inference widens too far (e.g. [] as never[]).

Union and literal types

type Status = 'idle' | 'loading' | 'error';
type Id = string | number;

function formatId(id: Id) {
  return typeof id === 'number' ? id.toFixed(0) : id;
}

Literal unions model fixed sets of strings or numbers—common for Redux-style state machines and component props.

Narrowing

Inside a block, TypeScript refines union types using control flow.

function printLength(x: string | string[] | null) {
  if (x === null) return;
  if (typeof x === 'string') {
    console.log(x.length);
    return;
  }
  console.log(x.length); // string[]
}

// Type predicate: still validate shape; `'name' in v` alone is not enough
function isUser(v: unknown): v is { name: string } {
  if (typeof v !== 'object' || v === null) return false;
  const o = v as Record<string, unknown>;
  return typeof o.name === 'string';
}

Patterns: typeof, === / !==, instanceof, in, discriminated unions with a shared tag field, and user-defined type predicates (v is Type).

Security: Type predicates only affect compile-time types. For data from networks, storage, or users, pair narrowing with real runtime checks (schema validators, structured checks). Never trust as SomeType or a loose in check alone for auth or sensitive logic.

type vs interface

interface Box {
  value: string;
}
// declaration merging: another `interface Box` adds members

type Point = { x: number; y: number };
type Result = { ok: true; data: string } | { ok: false; error: string };

interface: extendable, can merge across files (use carefully). type: unions, intersections, mapped types, and tuples—more expressive for advanced patterns. For plain object shapes, either works; teams often pick one style per codebase.

Strict compiler options

In tsconfig.json, "strict": true enables a bundle of checks. Especially valuable:

  • strictNullChecksnull and undefined are not assignable to every type
  • noImplicitAny — implicit any is an error
  • noUncheckedIndexedAccess — indexed access may be undefined