Skip to main content

Documentation Index

Fetch the complete documentation index at: https://resources.devweekends.com/llms.txt

Use this file to discover all available pages before exploring further.

TypeScript Crash Course

TypeScript is JavaScript with superpowers. It adds static typing to JavaScript, catching errors at compile time instead of runtime. If you’re building anything serious — from React apps to Node.js APIs — TypeScript is the industry standard.
Think of TypeScript as a contract system for your code. In plain JavaScript, passing an object to a function is like handing someone an unmarked envelope — they have no idea what is inside until they open it (at runtime). TypeScript puts a label on every envelope: “This contains a User with id, name, and email.” If you try to pass the wrong envelope, the compiler catches it before anyone opens it. This crash course takes you from basic types to advanced patterns like generics, decorators, and type gymnastics.

Why TypeScript?

TypeScript has become the default choice for professional JavaScript development.

Catch Errors Early

Static typing catches bugs before your code runs. No more “undefined is not a function” in production.

Better IDE Experience

Autocomplete, refactoring, and inline documentation powered by the type system.

Scales with Your Team

Types serve as documentation. Large codebases become manageable and self-documenting.

JavaScript Compatible

TypeScript is a superset of JavaScript. Any valid JS is valid TS. Migrate gradually.

Course Roadmap

We’ll build your TypeScript knowledge from the ground up.
1

Fundamentals

Types, type inference, and basic annotations. Start Learning
2

Functions & Types

Function types, overloads, generics basics, and type guards. Master Functions
3

Objects & Interfaces

Interfaces, type aliases, optional properties, and index signatures. Define Structures
4

Classes & OOP

Classes, access modifiers, abstract classes, and decorators. Go Object-Oriented
5

Advanced Types

Union, intersection, conditional types, mapped types, and utility types. Level Up
6

Generics Deep Dive

Generic functions, classes, constraints, and real-world patterns. Master Generics
7

Modules & Configuration

ES modules, namespaces, tsconfig.json, and project setup. Configure Projects

Prerequisites

  • Strong understanding of JavaScript (ES6+).
  • Familiarity with Node.js and npm.
  • Basic understanding of object-oriented concepts.
  • A code editor with TypeScript support (VS Code recommended).

The TypeScript Philosophy

“TypeScript is a strict syntactical superset of JavaScript that adds optional static typing.” — Microsoft
TypeScript’s design goals:
  • Statically identify constructs that are likely to be errors.
  • Provide a structuring mechanism for larger pieces of code.
  • Impose no runtime overhead on emitted programs.
  • Align with current and future ECMAScript proposals.
// A taste of TypeScript

// An interface is a contract: any object claiming to be a "User"
// MUST have exactly these properties with these types.
interface User {
  id: number;
  name: string;
  email: string;
  role: 'admin' | 'user' | 'guest'; // Union type: only these 3 strings are allowed
}

// Parameter types and return types are explicit -- no guessing what this function expects
function greetUser(user: User): string {
  return `Hello, ${user.name}! You are logged in as ${user.role}.`;
}

// TypeScript checks this object against the User interface at compile time.
// Missing a property? Wrong type? You will know before the code ever runs.
const user: User = {
  id: 1,
  name: 'Alice',
  email: 'alice@example.com',
  role: 'admin'
};

console.log(greetUser(user)); // "Hello, Alice! You are logged in as admin."

Setting Up TypeScript

Installation

# Install globally
npm install -g typescript

# Check version
tsc --version

# Initialize a project
tsc --init

Your First TypeScript File

// hello.ts
function greet(name: string): string {
  return `Hello, ${name}!`;
}

console.log(greet('World'));
# Compile to JavaScript
tsc hello.ts

# Run the compiled JS
node hello.js

Using ts-node (No Compile Step)

# Install ts-node
npm install -g ts-node

# Run TypeScript directly
ts-node hello.ts
Pro Tip: Use tsx for even faster execution: npm install -g tsx && tsx hello.ts

TypeScript vs JavaScript

FeatureJavaScriptTypeScript
Type SystemDynamicStatic (optional)
Error DetectionRuntimeCompile time
IDE SupportBasicRich (autocomplete, refactoring)
Learning CurveLowerHigher (but worth it)
CompilationNot requiredRequired (to JS)
AdoptionUniversalGrowing rapidly

Let’s dive into the fundamentals and start writing type-safe code!

Interview Deep-Dive

Strong Answer:This is a surprisingly common objection, and it reveals a misunderstanding of where TypeScript’s value lies. The types disappearing at runtime is a feature, not a weakness.
  • Types are a development-time safety net: TypeScript catches an entire category of bugs before the code ever executes — null reference errors, misspelled property names, wrong argument types, missing function parameters. In a 2019 study by Airbnb, 38% of bugs that made it to production could have been prevented by TypeScript. That is not “extra work” — that is preventing on-call pages.
  • Zero runtime cost: Because types are erased during compilation, TypeScript adds exactly zero bytes to your production bundle and zero milliseconds to your execution time. Compare this to runtime validation libraries (Joi, Zod) that add bundle size and CPU overhead. TypeScript gives you the safety for free at build time.
  • IDE experience is the real killer feature: Autocomplete, inline documentation, safe refactoring (rename a property and every usage updates), and jump-to-definition across the codebase. These are not nice-to-haves — they fundamentally change how fast a developer can navigate and modify a large codebase. A team of 10 engineers working on 200K lines of untyped JavaScript spends a meaningful percentage of their time reading code to understand data shapes. TypeScript makes the shapes self-documenting.
  • The “extra work” argument only holds for toy projects: For a 50-line script, TypeScript is overhead. For a 50,000-line application with 10 contributors, it is the difference between “I can confidently refactor this module” and “I am afraid to touch this code because I do not know what will break.”
The trade-off is real but asymmetric: you invest time upfront writing types, and you save multiples of that time downstream in debugging, onboarding, and refactoring.Follow-up: When would you genuinely recommend NOT using TypeScript?Quick prototypes or throwaway scripts where the code will live less than a week and be written by one person. One-off data migration scripts. Small Lambda functions under 100 lines where the input/output shapes are trivial. The decision threshold is roughly: if the code will be maintained by someone other than the original author, or will live longer than a sprint, TypeScript pays for itself.
Strong Answer:This is one of the most fundamental concepts in TypeScript and the source of many “it compiles but it should not” surprises for developers coming from Java or C#.
  • Structural typing (TypeScript): Two types are compatible if their shapes match — if they have the same properties with the same types, they are interchangeable regardless of their names. If I define interface Dog { name: string } and interface Cat { name: string }, a Cat can be assigned to a Dog variable because they have the same structure. TypeScript does not care what you called the type; it cares what the type contains.
  • Nominal typing (Java): Two types are compatible only if they are explicitly declared as related (via extends or implements). Even if Dog and Cat have identical fields, they are different types because they have different names.
  • Why it matters for design: In TypeScript, you cannot rely on type names for safety. If you have type UserId = string and type OrderId = string, TypeScript treats them as interchangeable — you can pass an OrderId where a UserId is expected. In Java, these would be distinct types that the compiler enforces. To get nominal-like behavior in TypeScript, you need the “branded types” pattern: type UserId = string & { readonly __brand: 'UserId' }.
  • The upside of structural typing: It makes TypeScript incredibly flexible for working with JavaScript’s duck-typed ecosystem. You can define an interface for a library’s callback shape without importing the library’s types. If the callback matches your interface, TypeScript accepts it. This is why TypeScript integrates so smoothly with existing JavaScript — it does not require every library to formally declare type relationships.
Follow-up: Show me how you would implement branded types to prevent mixing up UserId and OrderId.The pattern uses intersection with a phantom property that exists only at the type level: type UserId = string & { readonly __brand: unique symbol }. You create factory functions: function createUserId(id: string): UserId { return id as UserId }. Now createUserId('123') returns a UserId, and passing it to a function expecting OrderId produces a compile error. The __brand property does not exist at runtime — it is purely a type-level trick. This is the standard approach in production TypeScript codebases where domain safety matters (financial systems, multi-tenant platforms).
Strong Answer:The TypeScript compiler has four distinct stages, and understanding them explains why certain errors appear when they do.
  • Stage 1 — Lexing: The source code is broken into tokens (keywords, identifiers, operators, literals). let x: number = 5 becomes tokens: let, x, :, number, =, 5. This is a mechanical process that fails only on truly malformed input (unclosed strings, invalid characters).
  • Stage 2 — Parsing: Tokens are assembled into an Abstract Syntax Tree (AST). The parser understands JavaScript and TypeScript grammar — it knows that let x: number = 5 is a variable declaration with a type annotation. Syntax errors are caught here: missing semicolons, unmatched brackets, invalid expression structure.
  • Stage 3 — Type Checking: This is where TypeScript earns its keep. The type checker walks the AST and validates that every operation is type-safe. It resolves type references, checks assignment compatibility, infers types where annotations are missing, and reports the “red squiggle” errors you see in your IDE. This stage uses TypeScript’s structural type system, control flow analysis, and all the advanced type features (conditional types, mapped types, generics). This is by far the most computationally expensive stage.
  • Stage 4 — Emitting: The typed AST is transformed into JavaScript. All type annotations, interfaces, type aliases, and enums (for const enum) are stripped. The output is plain JavaScript that any runtime can execute. The emitter also generates .d.ts declaration files and source maps if configured.
The critical insight is type erasure: after Stage 4, there is no TypeScript. The runtime never sees types. This means you cannot use TypeScript types for runtime decisions — if (x instanceof MyType) does not work for interfaces because interfaces do not exist at runtime. You need runtime type guards (typeof, instanceof for classes, or custom validation functions).Follow-up: If type checking is the expensive stage, how do large projects manage compilation performance?Three strategies. First, --incremental mode: TypeScript caches the AST and type information from the previous compilation and only re-checks files that changed. This can reduce rebuild times from 30 seconds to under 2 seconds for a large project. Second, Project References with --build mode: split a monorepo into smaller sub-projects that are compiled independently. A change in packages/ui does not re-check packages/server. Third, tools like esbuild or swc that skip type checking entirely and only do Stage 1, 2, and 4 (lexing, parsing, emitting). They rely on tsc --noEmit running separately (often in CI) to catch type errors. This gives you sub-second build times during development while still catching type errors before merge.