programming4 min read

Tutorial: Learn TypeScript from Scratch (2026)

Tutorial: Learn TypeScript from Scratch (2026)

Published:  |  Category: Programming  |  Reading time: ~15 min
Tutorial: Learn TypeScript from Scratch (2026)

TypeScript adds static typing to JavaScript without changing the runtime behavior. I have used it in large-scale frontend applications, backend Node.js services, and even CLI tools. The type system catches entire categories of bugs at compile time — null references, misspelled property names, incorrect function arguments — that would otherwise surface in production.

What makes TypeScript special is its balance: it is gradual by default, sound where it matters, and deeply integrated with the JavaScript ecosystem. You can adopt it file by file in an existing project.

Types and Interfaces

TypeScript provides primitive types (string, number, boolean), object types, and union/intersection types. Interfaces define the shape of objects and can be extended. Type aliases create names for any type. Use interfaces for public API contracts and types for computed or complex types. Both support optional properties with ?.

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

type Status = 'active' | 'inactive' | 'banned';

type ApiResponse = {
  data: T;
  error: string | null;
};

const user: User = { id: 1, name: 'Alice' };
const resp: ApiResponse = { data: user, error: null };

Generics

Generics parameterize types, enabling reusable components that work with any type. Constraints with extends limit the type parameter. Generic functions, classes, and interfaces reduce duplication. TypeScript infers generic type arguments from usage in most cases.

function identity(arg: T): T {
  return arg;
}

function first(items: T): number {
  return items.length;
}

class Stack {
  private items: T[] = [];
  push(item: T) { this.items.push(item); }
  pop(): T | undefined { return this.items.pop(); }
}

const numStack = new Stack();

Enums

Enums define a set of named constants. Numeric enums auto-increment from 0. String enums have more readable runtime values. Const enums are inlined at compile time and have no runtime representation. Enums can also have computed members. Use string enums when the enum value matters at runtime.

enum Direction {
  Up,
  Down,
  Left,
  Right
}

enum Color {
  Red = '#FF0000',
  Green = '#00FF00',
  Blue = '#0000FF'
}

const enum Size {
  Small = 1,
  Medium = 2,
  Large = 3
}

console.log(Direction.Up);   // 0
console.log(Color.Red);      // #FF0000

Decorators

Decorators are functions that modify classes, methods, properties, or parameters. They are an experimental feature enabled via experimentalDecorators. A decorator receives the target and metadata; it can wrap, replace, or annotate. Commonly used in Angular and NestJS for dependency injection, routing, and validation.

function log(target: any, key: string, descriptor: PropertyDescriptor) {
  const original = descriptor.value;
  descriptor.value = function (...args: any[]) {
    console.log(`Calling ${key} with`, args);
    return original.apply(this, args);
  };
}

class MathOps {
  @log
  add(a: number, b: number): number {
    return a + b;
  }
}

const ops = new MathOps();
ops.add(2, 3); // logs: Calling add with [2, 3]

Type Narrowing

Narrowing refines a broad type to a more specific one based on control flow. Use typeof guards for primitives, instanceof for classes, and in for property checks. Discriminated unions use a literal property to distinguish variants. User-defined type guards with is return type predicates give you full control.

type Shape =
  | { kind: 'circle'; radius: number }
  | { kind: 'rect'; width: number; height: number };

function area(s: Shape): number {
  switch (s.kind) {
    case 'circle':
      return 3.14 * s.radius ** 2;
    case 'rect':
      return s.width * s.height;
  }
}

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

Utility Types

TypeScript provides built-in utility types for common transformations. Partial<T> makes all properties optional. Required<T> makes all required. Pick<T, K> selects a subset. Omit<T, K> removes keys. Record<K, T> creates an object type. Readonly<T> prevents mutation.

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

type PartialUser = Partial;
type UserName = Pick;
type WithoutEmail = Omit;
type UserMap = Record;
type ImmutableUser = Readonly;

const updates: PartialUser = { name: 'Bob' };
const map: UserMap = { '1': { id: 1, name: 'A', email: 'a@b.com' } };

Frequently Asked Questions

Interface vs type alias?

Interfaces can be extended and merged. Types cannot be reopened but can represent unions, tuples, and computed types. Prefer interface for object shapes.

What is the unknown type?

unknown is the type-safe counterpart of any. You must narrow it before use. It forces type checks that any bypasses.

How strict is strict mode?

strict enables noImplicitAny, strictNullChecks, strictFunctionTypes, strictBindCallApply, and more. Always use it.

What does as const do?

Marks an expression as immutable and infers literal types instead of widened types like string. Useful for tuples and enums.

Originally published on Ayodhyyya. Last updated June 1, 2026.