TECHNICAL PRESENTATION

Introduction to
TypeScript

Typed JavaScript at Any Scale
types · generics · interfaces · advanced types · Node · React
02

Agenda

Foundations

  • What is TypeScript & why it exists
  • Type annotations & inference
  • Interfaces & type aliases
  • Functions & generics

Type System Deep Dive

  • Enums & literal types
  • Classes & access modifiers
  • Type guards & narrowing
  • Advanced types & mapped types

Ecosystem Integration

  • Module system & declaration files
  • TypeScript with Node.js
  • TypeScript with Express
  • TypeScript with React

Production

  • Strict mode & compiler options
  • Error handling patterns
  • Migration strategy
  • Summary & next steps
03

What Is TypeScript?

TypeScript is a strict syntactical superset of JavaScript developed by Anders Hejlsberg at Microsoft (released 2012). Every valid JS file is valid TS. TypeScript compiles (tsc) to plain JavaScript — types are erased at runtime.

The key insight: JavaScript's dynamic typing becomes a liability at scale. TypeScript adds a static type system that catches errors at compile time, enables powerful IDE tooling, and serves as living documentation.

TypeScript is NOT

  • A new language (it's a JS superset — all JS is valid TS)
  • A runtime (types are erased during compilation)
  • Only for large projects (benefits start from line 1)

Key Properties

  • Static typing — errors at compile time
  • Type inference — less boilerplate than Java/C#
  • Structural typing — shape matters, not name
  • IDE integration — autocomplete, refactoring
  • Gradual adoption — use as much or little as you want

Compilation Pipeline

# Install
npm install -D typescript

# Compile
npx tsc index.ts          # → index.js
npx tsc --watch           # recompile on change
npx tsc --init            # generate tsconfig.json
04

Type Annotations & Inference

Explicit Annotations

// Primitives
let name: string = "Alice";
let age: number = 30;
let active: boolean = true;
let data: null = null;
let val: undefined = undefined;

// Arrays & tuples
let ids: number[] = [1, 2, 3];
let pair: [string, number] = ["age", 30];

// Object type
let user: { name: string; age: number } = {
  name: "Alice",
  age: 30,
};

Type Inference

// TS infers types automatically
let city = "London";        // string
let count = 42;             // number
let items = [1, 2, 3];     // number[]

// Inference from return values
function double(n: number) {
  return n * 2;             // returns number
}

// Contextual typing
const names = ["Alice", "Bob"];
names.forEach(name => {
  console.log(name.toUpperCase()); // name: string
});

any

Opts out of type checking entirely. Avoid in production code — defeats the purpose of TypeScript.

let x: any = "hello";
x = 42;  // no error
x.foo(); // no error (crashes at runtime)

unknown

Type-safe counterpart of any. Must narrow before use.

let x: unknown = getInput();
// x.toUpperCase(); // Error!
if (typeof x === "string") {
  x.toUpperCase();  // OK after narrowing
}

never

Represents values that never occur. Used for exhaustive checks and functions that never return.

function fail(msg: string): never {
  throw new Error(msg);
}
// Exhaustive check
type Shape = "circle" | "square";
function area(s: Shape) {
  switch (s) {
    case "circle": return /*...*/;
    case "square": return /*...*/;
    default: const _: never = s;
  }
}
05

Interfaces & Type Aliases

Interfaces

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

// Extending
interface Admin extends User {
  permissions: string[];
}

// Declaration merging (interfaces only)
interface User {
  avatar?: string;  // merges with above
}

Type Aliases

type ID = string | number;   // union type

type Point = {
  x: number;
  y: number;
};

// Intersection types
type Timestamped = Point & {
  createdAt: Date;
};

// Mapped from existing types
type ReadonlyUser = Readonly<User>;
type PartialUser = Partial<User>;

Structural Typing

TypeScript uses structural typing (duck typing). If the shape matches, it's assignable — regardless of declared type name.

interface Point { x: number; y: number }
interface Coord { x: number; y: number }

let p: Point = { x: 1, y: 2 };
let c: Coord = p;  // OK — same shape

Union & Discriminated Unions

type Result =
  | { status: "ok";    data: string }
  | { status: "error"; message: string };

function handle(r: Result) {
  if (r.status === "ok") {
    console.log(r.data);      // narrowed
  } else {
    console.log(r.message);   // narrowed
  }
}
06

Functions

Parameter & Return Types

// Typed parameters and return
function greet(name: string): string {
  return `Hello, ${name}!`;
}

// Optional & default parameters
function log(msg: string, level = "info"): void {
  console.log(`[${level}] ${msg}`);
}

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

// Function type alias
type Comparator<T> = (a: T, b: T) => number;

Overloads

// Overload signatures
function parse(input: string): number;
function parse(input: string[]): number[];
// Implementation signature
function parse(input: string | string[]): number | number[] {
  if (Array.isArray(input)) {
    return input.map(Number);
  }
  return Number(input);
}

parse("42");        // returns number
parse(["1", "2"]);  // returns number[]

Generic Functions

function first<T>(arr: T[]): T | undefined {
  return arr[0];
}

first([1, 2, 3]);      // number | undefined
first(["a", "b"]);     // string | undefined

Callback Typing Pattern

function fetchData<T>(url: string, transform: (raw: unknown) => T): Promise<T> {
  return fetch(url).then(res => res.json()).then(transform);
}
// Usage — TS infers return as Promise<User[]>
const users = await fetchData("/api/users", (data) => data as User[]);
07

Generics

Type Parameters & Constraints

// Basic generic
interface Box<T> {
  value: T;
}
const strBox: Box<string> = { value: "hello" };

// Constraints with extends
function getLength<T extends { length: number }>(
  item: T
): number {
  return item.length;
}
getLength("hello");   // 5
getLength([1, 2, 3]); // 3
// getLength(42);     // Error: no .length

// keyof constraint
function getProperty<T, K extends keyof T>(
  obj: T, key: K
): T[K] {
  return obj[key];
}

Utility Types

UtilityDescriptionExample
Partial<T>All props optionalPartial<User>
Required<T>All props requiredRequired<Config>
Pick<T,K>Select subset of keysPick<User, "id" | "name">
Omit<T,K>Remove keysOmit<User, "password">
Record<K,V>Map keys to valuesRecord<string, number>
Readonly<T>All props readonlyReadonly<State>
ReturnType<F>Infer return typeReturnType<typeof fn>
Parameters<F>Infer param typesParameters<typeof fn>

Real-World Generic

// API response wrapper
interface ApiResponse<T> {
  data: T;
  status: number;
  timestamp: Date;
}

async function fetchApi<T>(
  url: string
): Promise<ApiResponse<T>> {
  const res = await fetch(url);
  const data = await res.json();
  return { data, status: res.status, timestamp: new Date() };
}
08

Enums & Literal Types

Numeric Enums

enum Direction {
  Up,      // 0
  Down,    // 1
  Left,    // 2
  Right,   // 3
}

let d: Direction = Direction.Up;
console.log(Direction[0]); // "Up"
// Reverse mapping works

String Enums

enum Status {
  Active  = "ACTIVE",
  Paused  = "PAUSED",
  Deleted = "DELETED",
}

// No reverse mapping
// More readable in logs/JSON
// Use for API contracts

const Enums

const enum HttpMethod {
  GET  = "GET",
  POST = "POST",
  PUT  = "PUT",
}

// Inlined at compile time
// No runtime object generated
let m = HttpMethod.GET;
// Compiles to: let m = "GET";

Literal Types — Often Preferred Over Enums

// String literal union — no runtime cost
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";
type Status = "idle" | "loading" | "success" | "error";

function request(method: HttpMethod, url: string) { /* ... */ }
request("GET", "/api/users");    // OK
// request("PATCH", "/api");     // Error!

Discriminated Unions

type Shape =
  | { kind: "circle";    radius: number }
  | { kind: "rectangle"; width: number; height: number }
  | { kind: "triangle";  base: number; height: number };

function area(shape: Shape): number {
  switch (shape.kind) {
    case "circle":    return Math.PI * shape.radius ** 2;
    case "rectangle": return shape.width * shape.height;
    case "triangle":  return 0.5 * shape.base * shape.height;
  }
}
09

Classes & Access Modifiers

class Animal {
  // Access modifiers
  public name: string;
  protected speed: number;
  private _id: number;

  constructor(name: string, speed: number) {
    this.name = name;
    this.speed = speed;
    this._id = Math.random();
  }

  // Shorthand — declare in constructor
  // constructor(public name: string) {}

  move(): string {
    return `${this.name} moves at ${this.speed}km/h`;
  }
}

class Dog extends Animal {
  constructor(name: string) {
    super(name, 40);
  }

  bark(): string {
    return `${this.name} says woof!`;
    // this.speed OK (protected)
    // this._id  Error (private)
  }
}

Abstract Classes

abstract class Shape {
  abstract area(): number;
  abstract perimeter(): number;

  describe(): string {
    return `Area: ${this.area().toFixed(2)}`;
  }
}

class Circle extends Shape {
  constructor(private radius: number) { super(); }
  area() { return Math.PI * this.radius ** 2; }
  perimeter() { return 2 * Math.PI * this.radius; }
}

Implements Interface

interface Serializable {
  serialize(): string;
  deserialize(data: string): void;
}

class Config implements Serializable {
  constructor(private data: Record<string, unknown>) {}

  serialize(): string {
    return JSON.stringify(this.data);
  }

  deserialize(raw: string): void {
    this.data = JSON.parse(raw);
  }
}
ModifierClassSubclassOutside
publicYesYesYes
protectedYesYesNo
privateYesNoNo
10

Type Guards & Narrowing

Built-in Guards

function process(value: string | number | Date) {
  // typeof guard
  if (typeof value === "string") {
    return value.toUpperCase();   // string
  }
  // instanceof guard
  if (value instanceof Date) {
    return value.toISOString();   // Date
  }
  return value.toFixed(2);       // number
}

// "in" operator guard
interface Fish { swim(): void }
interface Bird { fly(): void }

function move(animal: Fish | Bird) {
  if ("swim" in animal) {
    animal.swim();                // Fish
  } else {
    animal.fly();                 // Bird
  }
}

Custom Type Predicates

// Type predicate function
function isString(val: unknown): val is string {
  return typeof val === "string";
}

// Usage — narrows the type
function process(input: unknown) {
  if (isString(input)) {
    console.log(input.toUpperCase()); // string
  }
}

// Filter with type predicate
const mixed: (string | number)[] = [1, "a", 2, "b"];
const strings = mixed.filter(
  (x): x is string => typeof x === "string"
);
// strings: string[]

Assertion Functions

function assertDefined<T>(
  val: T | null | undefined,
  msg?: string
): asserts val is T {
  if (val == null) throw new Error(msg ?? "Undefined");
}

const user = getUser(); // User | null
assertDefined(user, "User not found");
user.name; // OK — narrowed to User
11

Advanced Types

Mapped Types

// Make all properties optional (how Partial works)
type MyPartial<T> = {
  [K in keyof T]?: T[K];
};

// Make all properties nullable
type Nullable<T> = {
  [K in keyof T]: T[K] | null;
};

// Remap keys with "as"
type Getters<T> = {
  [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
// { getName: () => string; getAge: () => number }

Template Literal Types

type EventName = `${"click" | "focus"}_${"start" | "end"}`;
// "click_start" | "click_end" | "focus_start" | "focus_end"

type CSSValue = `${number}${"px" | "em" | "rem" | "%"}`;
const width: CSSValue = "100px";    // OK
// const bad: CSSValue = "100vw";   // Error

Conditional Types

// T extends U ? X : Y
type IsString<T> = T extends string ? true : false;

type A = IsString<string>;  // true
type B = IsString<number>;  // false

// Extract / Exclude (built-in)
type T1 = Extract<"a" | "b" | "c", "a" | "c">; // "a" | "c"
type T2 = Exclude<"a" | "b" | "c", "a">;        // "b" | "c"

infer Keyword

// Extract return type of a function
type MyReturnType<T> = T extends (...args: any[]) => infer R
  ? R
  : never;

type R1 = MyReturnType<() => string>;  // string

// Unpack Promise
type Awaited<T> = T extends Promise<infer U>
  ? Awaited<U>   // recursive for nested Promises
  : T;

type R2 = Awaited<Promise<Promise<number>>>;  // number
12

Module System

Import / Export

// Named exports
export interface User { id: number; name: string; }
export function createUser(name: string): User {
  return { id: Date.now(), name };
}

// Default export
export default class UserService { /* ... */ }

// Re-export
export { User as AppUser } from "./models";
export * from "./utils";

// Type-only imports (erased at compile time)
import type { User } from "./models";
import { type User, createUser } from "./models";

Declaration Files (.d.ts)

Type definitions for JavaScript libraries. Contain only type information, no implementation.

// globals.d.ts
declare const API_URL: string;
declare function analytics(event: string): void;

// module.d.ts
declare module "my-lib" {
  export function parse(input: string): object;
  export const version: string;
}

@types & DefinitelyTyped

  • npm i -D @types/express — community types
  • 60,000+ packages on DefinitelyTyped
  • Auto-discovered by TS if in node_modules/@types
  • Use typeRoots in tsconfig to customise

Ambient Modules

// Handle non-TS imports (CSS, SVG, JSON)
declare module "*.css" {
  const classes: Record<string, string>;
  export default classes;
}
declare module "*.svg" {
  const src: string;
  export default src;
}
13

TypeScript with Node.js

tsconfig.json for Node

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "Node16",
    "moduleResolution": "Node16",
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "declaration": true,
    "sourceMap": true,
    "paths": {
      "@/*": ["./src/*"],
      "@utils/*": ["./src/utils/*"]
    }
  },
  "include": ["src/**/*"]
}

Development Runners

ToolCommandNotes
ts-nodenpx ts-node src/index.tsJIT compilation, slower startup
tsxnpx tsx src/index.tsesbuild-powered, fast
tsc --watchnpx tsc -wCompile + nodemon for restart
Node 22+node --experimental-strip-types index.tsNative TS stripping (experimental)

Path Aliases with ts-node

// package.json
{
  "scripts": {
    "dev": "tsx watch src/index.ts",
    "build": "tsc",
    "start": "node dist/index.js"
  }
}

// tsconfig-paths registers @ aliases at runtime
// tsx handles them automatically

Essential Dev Dependencies

npm i -D typescript @types/node tsx
npm i -D @tsconfig/node22  # shared base config
14

TypeScript with Express

Typed Request / Response

import express, {
  Request, Response, NextFunction
} from "express";

interface CreateUserBody {
  name: string;
  email: string;
}

interface UserParams {
  id: string;
}

// Typed route handler
app.post(
  "/api/users",
  (req: Request<{}, {}, CreateUserBody>, res: Response) => {
    const { name, email } = req.body; // typed!
    // ...
    res.status(201).json({ id: 1, name, email });
  }
);

app.get(
  "/api/users/:id",
  (req: Request<UserParams>, res: Response) => {
    const userId = req.params.id; // string
    // ...
  }
);

Typed Middleware

// Extend Express Request
declare global {
  namespace Express {
    interface Request {
      user?: { id: number; role: string };
    }
  }
}

// Auth middleware
function auth(
  req: Request, res: Response, next: NextFunction
): void {
  const token = req.headers.authorization;
  if (!token) {
    res.status(401).json({ error: "Unauthorized" });
    return;
  }
  req.user = verifyToken(token); // typed!
  next();
}

// Error middleware (4 params)
function errorHandler(
  err: Error, req: Request,
  res: Response, _next: NextFunction
): void {
  res.status(500).json({ error: err.message });
}

Setup

npm i express
npm i -D @types/express typescript tsx
15

TypeScript with React

Components & Props

// Props interface
interface ButtonProps {
  label: string;
  variant?: "primary" | "secondary";
  disabled?: boolean;
  onClick: (e: React.MouseEvent) => void;
  children?: React.ReactNode;
}

// Function component (preferred over React.FC)
function Button({ label, variant = "primary",
  disabled, onClick, children }: ButtonProps) {
  return (
    <button
      className={variant}
      disabled={disabled}
      onClick={onClick}
    >
      {children ?? label}
    </button>
  );
}

Generic Component

interface ListProps<T> {
  items: T[];
  renderItem: (item: T) => React.ReactNode;
}

function List<T>({ items, renderItem }: ListProps<T>) {
  return <ul>{items.map(renderItem)}</ul>;
}

// Usage — T inferred as User
<List items={users} renderItem={u => <li>{u.name}</li>} />

Hooks Typing

// useState with type parameter
const [user, setUser] = useState<User | null>(null);

// useRef
const inputRef = useRef<HTMLInputElement>(null);

// useReducer
type Action =
  | { type: "increment" }
  | { type: "set"; payload: number };

function reducer(state: number, action: Action): number {
  switch (action.type) {
    case "increment": return state + 1;
    case "set":       return action.payload;
  }
}
const [count, dispatch] = useReducer(reducer, 0);

Common Event Types

EventType
ClickReact.MouseEvent<HTMLButtonElement>
ChangeReact.ChangeEvent<HTMLInputElement>
SubmitReact.FormEvent<HTMLFormElement>
KeyboardReact.KeyboardEvent<HTMLInputElement>
16

Strict Mode & Compiler Options

What "strict": true Enables

FlagEffect
strictNullChecksnull/undefined not assignable to other types
noImplicitAnyError on inferred any
strictFunctionTypesContravariant parameter checking
strictPropertyInitializationClass props must be initialised
noImplicitThisError on this with implicit any
alwaysStrictEmit "use strict"
useUnknownInCatchVariablescatch(e) is unknown

Key Compiler Options

{
  "compilerOptions": {
    // Type checking
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "exactOptionalPropertyTypes": true,
    "noFallthroughCasesInSwitch": true,

    // Output
    "target": "ES2022",
    "module": "Node16",
    "outDir": "./dist",
    "declaration": true,
    "sourceMap": true,

    // Module resolution
    "moduleResolution": "Node16",
    "esModuleInterop": true,
    "resolveJsonModule": true,
    "isolatedModules": true,

    // Emit
    "removeComments": true,
    "skipLibCheck": true
  }
}

Recommendation

Start every new project with "strict": true. Add "noUncheckedIndexedAccess": true for array/object safety. Use "exactOptionalPropertyTypes": true to distinguish undefined from missing. These flags catch entire categories of bugs at zero runtime cost.

17

Error Handling Patterns

Result Type Pattern

// Discriminated union for success / failure
type Result<T, E = Error> =
  | { ok: true;  value: T }
  | { ok: false; error: E };

function parseJSON(input: string): Result<unknown> {
  try {
    return { ok: true, value: JSON.parse(input) };
  } catch (e) {
    return { ok: false, error: e as Error };
  }
}

const result = parseJSON('{"name":"Alice"}');
if (result.ok) {
  console.log(result.value); // narrowed to unknown
} else {
  console.error(result.error.message);
}

Branded Types

// Prevent mixing IDs from different domains
type UserId = string & { readonly __brand: "UserId" };
type OrderId = string & { readonly __brand: "OrderId" };

function createUserId(id: string): UserId {
  // validate...
  return id as UserId;
}

function getUser(id: UserId) { /* ... */ }
// getUser(orderId);  // Error — type mismatch!

Exhaustive Checks with never

type Status = "active" | "paused" | "deleted";

function assertNever(x: never): never {
  throw new Error(`Unexpected value: ${x}`);
}

function handleStatus(status: Status): string {
  switch (status) {
    case "active":  return "Running";
    case "paused":  return "On hold";
    case "deleted": return "Removed";
    default: return assertNever(status);
    // If a new status is added, TS errors here
  }
}

Typed Error Hierarchy

class AppError extends Error {
  constructor(
    message: string,
    public readonly code: string,
    public readonly statusCode: number
  ) {
    super(message);
    this.name = "AppError";
  }
}

class NotFoundError extends AppError {
  constructor(resource: string) {
    super(`${resource} not found`, "NOT_FOUND", 404);
  }
}

class ValidationError extends AppError {
  constructor(public readonly fields: string[]) {
    super("Validation failed", "VALIDATION", 400);
  }
}
18

Migration Strategy

Migrating a JavaScript project to TypeScript doesn't have to be all-or-nothing. Use a gradual, incremental approach.

Step-by-Step Migration

  • 1. Add tsconfig.json with "allowJs": true
  • 2. Rename files .js.ts one at a time
  • 3. Start with "strict": false, enable flags incrementally
  • 4. Replace any with unknown, then add proper types
  • 5. Add types to function signatures first (biggest ROI)
  • 6. Enable "strict": true once most files are typed

JSDoc Types (no rename needed)

// @ts-check — enables TS checking in JS files

/** @type {string} */
let name = "Alice";

/**
 * @param {number} a
 * @param {number} b
 * @returns {number}
 */
function add(a, b) {
  return a + b;
}

Migration tsconfig.json

{
  "compilerOptions": {
    "allowJs": true,
    "checkJs": true,
    "strict": false,
    "noImplicitAny": false,
    "target": "ES2022",
    "module": "Node16",
    "outDir": "./dist",
    "rootDir": "./src",
    "esModuleInterop": true,
    "skipLibCheck": true
  },
  "include": ["src/**/*"]
}
PhaseActionRisk
1. SetupallowJs + checkJsZero
2. AnnotateJSDoc types in .js filesZero
3. Rename.js.ts, fix errorsLow
4. Strict flagsEnable one flag at a timeMedium
5. Full strict"strict": trueLow (if incremental)

Helpful Tools

  • ts-migrate (Airbnb) — auto-converts JS to TS
  • @ts-expect-error — suppress known issues temporarily
  • // @ts-ignore — last resort, avoid in production
19

Summary & Next Steps

Core Type System

  • Annotations, inference, any/unknown/never
  • Interfaces, type aliases, structural typing
  • Unions, intersections, discriminated unions
  • Generics, constraints, utility types

Advanced Features

  • Mapped types, conditional types, infer
  • Template literal types
  • Type guards, narrowing, assertion functions
  • Branded types, Result pattern

Ecosystem

  • Node.js: tsx, path aliases, tsconfig
  • Express: typed routes, middleware, params
  • React: props, hooks, generic components
  • Migration: allowJs, incremental adoption

Recommended Reading

  • TypeScript Handbook — typescriptlang.org/docs
  • Programming TypeScript — Boris Cherny, O'Reilly
  • Effective TypeScript — Dan Vanderkam, O'Reilly
  • Type Challenges — github.com/type-challenges
  • Total TypeScript — Matt Pocock (totaltypescript.com)

Key Takeaways

  • TypeScript catches bugs at compile time, not production
  • Start with "strict": true on every new project
  • Prefer unknown over any — always
  • Use discriminated unions over class hierarchies
  • Types are zero-cost — erased at compile time
  • Gradual migration is the safe path for existing codebases