TypeScript basics in one page.

Primitives

let n: number = 42;
let s: string = "hello";
let b: boolean = true;
let big: bigint = 100n;
let sym: symbol = Symbol("x");
let nothing: null = null;
let undef: undefined = undefined;

Prefer inference — annotate only when TS can’t figure it out.

const x = 42;             // x: 42 (literal)
let y = 42;               // y: number

Arrays / tuples

const xs: number[] = [1, 2, 3];
const ys: Array<number> = [1, 2];

// Tuple — fixed length, position-typed
const pair: [string, number] = ["age", 30];

// Readonly tuple
const rgb: readonly [number, number, number] = [255, 0, 0];

Object types

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

const u: User = { id: 1, name: "A", createdAt: new Date() };

Interfaces vs type aliases

interface User {
  id: number;
  name: string;
}

type User = { id: number; name: string };

Interfaces can be extended and merged:

interface User { id: number }
interface User { name: string }    // merged

interface Admin extends User { role: string }

Type aliases support unions, intersections, mapped types:

type Status = "active" | "inactive";
type Admin = User & { role: string };

Default to type. Use interface for public API shapes or class-implementable contracts.

Unions

type Id = string | number;

function format(id: Id): string {
  if (typeof id === "string") return id.toUpperCase();
  return id.toFixed();
}

Literal types

type Direction = "up" | "down" | "left" | "right";
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";

Functions

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

const sub = (a: number, b: number): number => a - b;

// Function type
type BinOp = (a: number, b: number) => number;
const mul: BinOp = (a, b) => a * b;

Optional and default params:

function greet(name: string, greeting = "Hello"): string {
  return `${greeting}, ${name}`;
}

function find(id: number, options?: { strict: boolean }) {}

Rest params:

function sum(...xs: number[]): number {
  return xs.reduce((a, b) => a + b, 0);
}

void / never

function log(msg: string): void { console.log(msg); }

function fail(msg: string): never {
  throw new Error(msg);
}

never = no value ever returned.

any / unknown

let a: any = "x";
a.foo.bar;              // no error (DANGEROUS)

let u: unknown = "x";
u.foo;                  // ERROR — must narrow first
if (typeof u === "string") u.toUpperCase();

Use unknown for “I don’t know yet”; never any.

Type assertions

const el = document.getElementById("x") as HTMLInputElement;
const n = "42" as unknown as number;     // double-cast escape hatch

// Avoid `<HTMLInputElement>` — clashes with JSX.

Narrowing

function f(x: string | number) {
  if (typeof x === "string") {
    x.toUpperCase();         // string
  } else {
    x.toFixed();             // number
  }
}

// instanceof
if (err instanceof Error) err.message;

// in
if ("name" in obj) obj.name;

// equality / discriminator
if (s.kind === "circle") s.radius;

Truthiness

if (s) {}                  // narrows to non-null/empty
if (s != null) {}          // != null catches both null and undefined

Type guards

function isUser(x: unknown): x is User {
  return typeof x === "object" && x !== null && "id" in x;
}

if (isUser(x)) {
  x.id;                   // narrowed to User
}

Enums (avoid; prefer unions)

enum Status { Active, Inactive }     // 0, 1
enum Role { Admin = "admin", User = "user" }

// Prefer:
type Status = "active" | "inactive";
const Status = { Active: "active", Inactive: "inactive" } as const;

as const

const config = {
  host: "localhost",
  port: 5432,
} as const;
// config.port is 5432, not number

Locks values to literals; makes objects deeply readonly.

satisfies (4.9+)

const config = {
  host: "localhost",
  port: 5432,
} satisfies Record<string, string | number>;
// Checks against the type but preserves literal types.

Non-null assertion (!)

const el = document.getElementById("x")!;  // assert non-null

Use sparingly — it’s an unchecked promise to TS.

Optional chaining + nullish coalescing

const name = user?.profile?.name;
const port = config.port ?? 5432;          // ?? only triggers for null/undef

Common mistakes

  • Annotating where inference is fine — extra noise.
  • any to silence errors — defer real fixes; use unknown.
  • <T> casts in .tsx files — clash with JSX. Use as T.
  • Enums in libraries — produce JS runtime code; unions are cheaper.
  • ! everywhere — defeats null safety.

Read this next

If you want my TS starter (strict tsconfig + Vitest + tsup), it’s at rajpoot.dev .


Building something AI-, backend-, or data-heavy and want a second pair of eyes? I do consulting and freelance work — see my projects and ways to reach me at rajpoot.dev .