Fundamentals

2 min read
Foundational1 min read
Rapid overview

Fundamentals

TL;DR

TypeScript fundamentals: type inference and annotations, unions and intersections, interfaces vs type aliases, generics with constraints, narrowing via guards and predicates, function overloads, class access modifiers, and core tsconfig settings. These are the building blocks for writing type-safe code and the vocabulary an interviewer expects before discussing advanced types or tooling.

How it works

Table of Contents

Types and Inference

let count = 0; // number inferred
let label: string = 'Tasks';
let isOpen = false; // boolean inferred

const ids = [1, 2, 3]; // number[] inferred
const mixed: (string | number)[] = [1, 'two'];

Use explicit annotations when inference is unclear or when defining public APIs.

Unions and Intersections

type Id = string | number;

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

type WithTimestamp = { createdAt: string };
type WithAuthor = { author: string };

type AuditRecord = WithTimestamp & WithAuthor;

Unions represent alternatives, intersections combine required fields.

Interfaces vs Type Aliases

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

interface Admin extends User {
  role: 'admin';
}

type Status = 'idle' | 'loading' | 'success' | 'error';

type ApiResponse = {
  status: Status;
  data?: unknown;
};

Use interfaces for object shapes you expect to extend, and type aliases for unions, primitives, and composable helpers.

Generics

function identity<T>(value: T): T {
  return value;
}

const name = identity('Ada');
const countValue = identity(42);

function mapValues<T, U>(items: T[], mapper: (item: T) => U): U[] {
  return items.map(mapper);
}

Add constraints to keep generics safe:

function toMap<T extends { id: string }>(items: T[]) {
  return new Map(items.map((item) => [item.id, item]));
}

Narrowing and Guards

type Result = { ok: true; value: string } | { ok: false; error: string };

function handleResult(result: Result) {
  if (result.ok) {
    return result.value;
  }
  return result.error;
}

function isString(value: unknown): value is string {
  return typeof value === 'string';
}

Use typeof, instanceof, in, and custom predicates to narrow types safely.

Functions and Overloads

function parseInput(input: string): number;
function parseInput(input: number): number;
function parseInput(input: string | number): number {
  return typeof input === 'string' ? Number(input) : input;
}

const fromString = parseInput('42');
const fromNumber = parseInput(42);

Overloads let you expose precise call signatures while implementing a single function body.

Classes and Access Modifiers

class Session {
  public readonly id: string;
  private token: string;

  constructor(id: string, token: string) {
    this.id = id;
    this.token = token;
  }

  refresh(newToken: string) {
    this.token = newToken;
  }
}

Use public, private, and protected to communicate intent and enforce encapsulation.

Modules and tsconfig Basics

export function toTitleCase(value: string) {
  return value.replace(/\b\w/g, (char) => char.toUpperCase());
}

import { toTitleCase } from './strings';

Common tsconfig.json settings for frontend apps:

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "ESNext",
    "moduleResolution": "Bundler",
    "strict": true,
    "noImplicitAny": true
  }
}

See also