Skip to main content

Command Palette

Search for a command to run...

Masterclass on TypeScript

Updated
•16 min read•View as Markdown
Masterclass on TypeScript
V
Hey everyone, my name is Ved and I am a passionate and curios developer, Currently learning and sharing my learnings in the best and easiest way possible

If JavaScript works, why was TypeScript created?

Today we'll answer exactly that, and along the way we'll understand how types work, why interfaces and generics exist, and how a TypeScript project runs in a browser.


Why TypeScript Exists

JavaScript works. Millions of apps run on it. So why did TypeScript need to exist at all?

The problem with plain JavaScript in large applications

JavaScript doesn't check what type of data you're working with. You can pass a number where a string was expected, and JavaScript won't stop you, it'll just let your app break somewhere down the line.

function getDiscountedPrice(price, discount) {
  return price - discount;
}

getDiscountedPrice(100, "20"); /// no error, but the math silently breaks

That runs fine, no warning, no error, right up until the output is wrong and you're left debugging why a price came out looking strange.

  • No safety net. Nothing stops you from passing the wrong data type.

  • Typos fail silently. Misspell a property name on an object, and JavaScript just gives you undefined, instead of telling you what went wrong.

  • Refactoring is scary. Rename a field on a User object, and you have to manually hunt down every place that used the old name.

  • Large codebases get hard to trust. With hundreds of files and multiple developers, it becomes nearly impossible to know what shape of data a function actually expects.

Runtime errors vs compile-time errors

  • A runtime error happens while your app is actually running, often in front of a real user. By the time you see it, the damage (a crash, bad data, a broken page) has already happened.

  • A compile-time error happens before your code ever runs, right in your editor, while you're still typing.

JavaScript TypeScript
Error found while running Error found while writing code
User sees a broken app You see a red squiggly line
Debugging happens in prod Debugging happens before you save the file

TypeScript's whole job is to catch as many bugs as possible at compile-time, before they ever get the chance to become a runtime error.

Benefits of static typing

  • Your editor knows exactly what properties and methods are available on a value, and can autocomplete them for you

  • Mistakes get caught immediately, not three weeks later in production

  • Function signatures become documentation, you can tell what a function expects just by looking at it

  • Refactoring becomes safe, since TypeScript will point out every place that breaks

How TypeScript improves developer productivity

It might feel like writing types is "extra work" at first, but it pays off fast. Less time spent debugging weird undefined errors, less time spent guessing what shape of data a function needs, and way more confidence when changing existing code, especially in a codebase you didn't write yourself.

TypeScript as a superset of JavaScript

TypeScript is not a different language. Every valid JavaScript file is already valid TypeScript. TypeScript just adds an optional layer of types on top.

                TypeScript
        ┌─────────────────────────┐
        │                          │
        │      JavaScript          │
        │   (all valid JS code)    │
        │                          │
        │   + Types                │
        │   + Interfaces           │
        │   + Generics             │
        │                          │
        └─────────────────────────┘

That's why TypeScript is called a superset of JavaScript. It takes everything JavaScript already does, and builds a safety layer on top.


Understanding Type Annotations

A type annotation is just you telling TypeScript, "this value is going to be this kind of data."

Adding types to variables

let username: string = "sarah";
let age: number = 28;
let isActive: boolean = true;

Now, if you try to assign the wrong type of value later, TypeScript stops you immediately.

username = 42; // Error: Type 'number' is not assignable to type 'string'

Function parameter types

This is where type annotations really start to matter, since functions are where most bugs like to hide.

function getDiscountedPrice(price: number, discount: number): number {
  return price - discount;
}

getDiscountedPrice(100, "20"); // Error: Argument of type 'string' is not assignable to type 'number'

Now that mistake from earlier gets caught immediately, instead of quietly producing a wrong price.

Function return types

You can also tell TypeScript what a function is supposed to return. This catches cases where a function accidentally returns the wrong thing.

function getUserName(user: { name: string }): string {
  return user.name;
}

If someone later changes this function to accidentally return a number, TypeScript will flag it right away.

Type inference

You don't actually have to annotate everything. TypeScript is smart enough to figure out the type on its own, just from the value you assign.

let price = 100; // TypeScript infers this is a number, no annotation needed

TypeScript still fully protects this variable, price = "100" would still throw an error, even though you never wrote : number yourself.

Explicit vs inferred types

Explicit types Inferred types
You write let age: number = 28 let age = 28
TypeScript figures it out No, you told it Yes, from the value
Useful when Function parameters, empty variables, complex objects Simple variables with an obvious starting value

A good rule of thumb: let TypeScript infer types when it easily can, and write explicit types when it can't, especially for function parameters, since TypeScript has no value to infer from there.


Interfaces vs Type Aliases

Once you're passing around real objects, like a User or a Product, you need a way to describe their shape. That's where interfaces and type aliases come in.

What interfaces are

An interface describes the shape of an object, what properties it has, and what type each property is.

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

const user: User = {
  id: 1,
  name: "Sarah",
  email: "sarah@example.com",
};

If user is missing a property, or has the wrong type for one, TypeScript will catch it.

What type aliases are

A type alias does something very similar, it gives a name to a type, but it's written with the type keyword instead.

type Product = {
  id: number;
  name: string;
  price: number;
};

const product: Product = {
  id: 101,
  name: "Wireless Mouse",
  price: 25,
};

Similarities between them

  • Both describe the shape of an object

  • Both can be used to type function parameters, variables, and return values

  • Both support optional properties, using a ?

interface Order {
  id: number;
  discount?: number; // optional, might not exist
}

Differences between them

Interface Type Alias
Can be re-opened and extended later Yes, interface User { ... } twice merges No, TypeScript will error on duplicate names
Can describe unions (`string number`) No
Can extend another type Yes, using extends Yes, using & (intersection)
Common use case Object shapes, especially for classes Object shapes, unions, and more complex combinations

When to use interfaces

Use interface when you're describing an object, especially one tied to a class, or one that might need to be extended later by other parts of the codebase, like a shared User type that different features add fields to over time.

When to use type aliases

Use type alias when you need to describe something beyond a plain object, like a union of a few possible values, or when you're combining multiple types together.


Union Types

What union types are

A union type says "this value can be one of a few different types." You write it using the | symbol.

let orderStatus: "pending" | "shipped" | "delivered";

orderStatus = "pending";   // fine
orderStatus = "cancelled"; // Error: not one of the allowed values

Combining multiple possible types

Unions aren't just for strings, they work with any types.

function printId(id: number | string) {
  console.log(id);
}

printId(101);       // fine
printId("ORD-101"); // also fine

Real-world use cases

Think about an Order in an e-commerce app. Its status is naturally one of a small, known set of values, this is exactly what union types are built for.

interface Order {
  id: number;
  status: "pending" | "shipped" | "delivered" | "cancelled";
}

Now, if anyone tries to set status to something outside that list, like a typo such as "deliverd", TypeScript catches it instantly.

Handling unions safely

When a value could be more than one type, you often need to check which one it actually is before using it.

function formatId(id: number | string) {
  if (typeof id === "number") {
    return `#${id}`;
  }
  return id.toUpperCase();
}

This is called narrowing, checking the type first, so TypeScript knows exactly which version of the value you're working with at each point in the function.

Union Type Visualization

id: number | string
        │
        ├── could be a number  ──▶  "#101"
        │
        └── could be a string  ──▶  "ORD-101".toUpperCase()

Intersection Types

What intersection types are

An intersection type combines multiple types into one, using the & symbol. Instead of "one of these," it means "all of these, at once."

type Timestamped = {
  createdAt: Date;
};

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

type ProductWithTimestamp = Product & Timestamped;

Combining multiple type definitions

A value typed as ProductWithTimestamp must now satisfy both Product and Timestamped at the same time.

const product: ProductWithTimestamp = {
  id: 101,
  name: "Wireless Mouse",
  createdAt: new Date(),
};

Miss any one of those fields, and TypeScript will flag it.

Creating reusable type structures

This is really useful for shared building blocks. Instead of repeating createdAt and updatedAt on every single type in your app, you write it once, and combine it in wherever it's needed.

type WithTimestamps = {
  createdAt: Date;
  updatedAt: Date;
};

type User = { id: number; name: string } & WithTimestamps;
type Order = { id: number; total: number } & WithTimestamps;

Practical examples

Think of an Order that needs both order details and customer details, combined into a single object for a receipt page:

type OrderDetails = { id: number; total: number };
type CustomerDetails = { name: string; email: string };

type Receipt = OrderDetails & CustomerDetails;
Intersection Type Visualization

OrderDetails { id, total }
       │
       │   &
       ▼
CustomerDetails { name, email }
       │
       ▼
Receipt { id, total, name, email }   ← must have everything, from both

Generic Functions

Why generics are needed

Say you write a function that returns the first item in an array.

function getFirstItem(items: number[]) {
  return items[0];
}

This only works for arrays of numbers. If you need the same logic for an array of User objects, or an array of Product objects, you'd have to write the same function over and over, once per type. That's exactly the problem generics solve.

Reusable type-safe functions

A generic function works with whatever type you give it, while still staying fully type-safe.

function getFirstItem<T>(items: T[]): T {
  return items[0];
}

getFirstItem<number>([1, 2, 3]);          // returns a number
getFirstItem<string>(["a", "b", "c"]);     // returns a string

You usually don't even need to specify <T> yourself, TypeScript can infer it from what you pass in.

getFirstItem([1, 2, 3]); // TypeScript infers T is number

Generic parameters

That T is called a generic parameter, it's just a placeholder for "whatever type gets passed in when this function is actually used." You can name it anything, T is just the common convention, short for "Type."

function wrapInArray<T>(value: T): T[] {
  return [value];
}

wrapInArray<Product>({ id: 101, name: "Wireless Mouse", price: 25 });

Generic constraints

Sometimes you want to say "this can be any type, but it has to at least have this property." That's what a generic constraint does, using extends.

function printName<T extends { name: string }>(item: T): void {
  console.log(item.name);
}

printName({ name: "Sarah", age: 28 }); // fine, has a name
printName({ id: 101 });                 // Error: missing 'name'

Real-world examples

Here's a very common one, a generic function that fetches data and types the result based on whatever you tell it to expect:

async function fetchData<T>(url: string): Promise<T> {
  const response = await fetch(url);
  return response.json();
}

const user = await fetchData<User>("/api/user/1");
const products = await fetchData<Product[]>("/api/products");

Same function, reused for completely different shapes of data, and TypeScript still knows exactly what comes back each time.

Generic Function Type Flow

fetchData<User>("/api/user/1")        fetchData<Product[]>("/api/products")
        │                                       │
        ▼                                       ▼
   T = User                               T = Product[]
        │                                       │
        ▼                                       ▼
 Promise<User>                          Promise<Product[]>

Understanding tsconfig.json

What tsconfig.json is

tsconfig.json is a single file that tells the TypeScript compiler how to treat your entire project, which files to include, how strict to be, what JavaScript version to output, and more.

your-project/
├── tsconfig.json
├── src/
│   ├── index.ts
│   └── user.ts

Why TypeScript projects need it

Without it, you'd have to pass a long list of options to the compiler by hand, every single time you run it. tsconfig.json lets you set all of that up once, and every file in your project automatically follows the same rules.

Common compiler options

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "ESNext",
    "strict": true,
    "outDir": "./dist",
    "rootDir": "./src"
  }
}

Strict mode

"strict": true turns on a whole bundle of extra safety checks at once, things like requiring every variable to have a known type, and disallowing accidentally using a value that might be null or undefined without checking it first. Almost every real-world TypeScript project keeps this on, it catches a huge number of bugs that would otherwise slip through.

Target configuration

"target" tells TypeScript which version of JavaScript to output. If you set it to "ES2020", TypeScript will use modern JavaScript features in its output. If you set it to something older, like "ES5", TypeScript will rewrite newer syntax into older, more widely-supported code, so it runs on older browsers too.

Module configuration

"module" controls how TypeScript handles import and export statements in the compiled output, since different environments (a browser, Node.js, a bundler) expect these to be written slightly differently.

Project-wide settings

Beyond individual options, tsconfig.json also controls which files are even part of your project:

{
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}

This tells TypeScript, "only check and compile files inside src, and ignore everything in node_modules and dist."

tsconfig.json Controlling Compilation

        tsconfig.json
              │
   ┌──────────┼──────────┐
   ▼          ▼          ▼
target      strict     include/exclude
   │          │              │
   ▼          ▼              ▼
Output JS   Type safety   Which files
 version     checks       get compiled

TypeScript Compilation Process

How TypeScript becomes JavaScript

TypeScript code doesn't run directly, it gets compiled into plain JavaScript first, using a tool called tsc (the TypeScript compiler).

// user.ts
function greet(name: string): string {
  return `Hello, ${name}`;
}
// user.js (after compilation)
function greet(name) {
  return `Hello, ${name}`;
}

Notice the types are gone in the output. That's because types only exist to help you while you're writing and checking your code, they don't do anything at runtime, so the compiler strips them out.

What happens during compilation

  1. TypeScript reads your .ts files

  2. It checks every type against how values are actually used, and reports any mismatches

  3. If there are no type errors (or you've chosen to ignore them), it strips out all the type annotations

  4. It outputs plain .js files, following whatever rules you set in tsconfig.json

Why browsers cannot run TypeScript directly

Browsers only understand JavaScript. They have no idea what : string or interface User means, that syntax doesn't exist in JavaScript at all. So TypeScript always has to be converted into plain JavaScript before it can actually run anywhere, in a browser, or in Node.js.

Build workflow overview

TypeScript Compilation Pipeline
 
Your Code (.ts files)
        │
        ▼
TypeScript Compiler (tsc)
        │
   reads tsconfig.json for rules
        │
   checks types, reports errors
        │
   strips out all type annotations
        │
        ▼
Plain JavaScript (.js files)
        │
        ▼
Runs in the browser or Node.js

This is why TypeScript is best thought of as a tool that sits around your development process, not a replacement for JavaScript. You write TypeScript to catch mistakes early, and by the time your code actually runs anywhere, it's just JavaScript again.


Quick Recap

  • TypeScript catches mistakes at compile-time, before your code ever runs, instead of letting them surface as runtime errors.

  • It's a superset of JavaScript, every JS file is already valid TypeScript.

  • Type annotations can be written explicitly, or left for TypeScript to infer on its own.

  • Interfaces and type aliases both describe the shape of an object, interfaces can be extended later, type aliases can describe unions and combinations.

  • Union types (|) mean "one of these." Intersection types (&) mean "all of these, combined."

  • Generics let you write one function that works safely across many different types, instead of duplicating logic per type.

  • tsconfig.json controls how your whole project compiles, strictness, target JavaScript version, module format, and which files are included.

  • TypeScript code always gets compiled down to plain JavaScript before it runs, since browsers and Node.js only understand JavaScript, not TypeScript syntax.

TypeScript isn't a new language, it's JavaScript with a safety net built in, that catches your mistakes while you're writing the code, instead of your users finding them for you.

JavaScript: Everything you should know

Part 27 of 27

This is your ultimate guide towards JavaScirpt, this series of articles include - JavaScript Operators: The Basics You Need to Know - Understanding Variables and Data Types in JavaScript - Template Literals in JavaScript - Control Flow in JavaScript: If, Else, and Switch Explained - Array Methods You Must Know - JavaScript Arrays 101 - Function Declaration vs Function Expression: What’s the Difference? - Arrow Functions in JavaScript: A Simpler Way to Write Functions - Understanding Objects in JavaScript - Spread vs Rest Operators in JavaScript - Destructuring in JavaScript - Map and Set in JavaScript - Understanding Object-Oriented Programming in JavaScript - Understanding the this Keyword in JavaScript - The new Keyword in JavaScript - The Magic of this, call(), apply(), and bind() in JavaScript - Array Flatten in JavaScript - String Polyfills and Common Interview Methods in JavaScript - Synchronous vs Asynchronous JavaScript - Callbacks in JavaScript: Why They Exist - JavaScript Promises Explained for Beginners - Promise Methods Explained: CID Analogy - Async/Await in JavaScript: Writing Cleaner Asynchronous Code - Error Handling in JavaScript: Try, Catch, Finally - JavaScript Modules: Import and Export Explained - The Part of Events You Never See The following 26 articles and topics are covered

Start from the beginning

Promise Methods Explained: CID Analogy

Pre Requisite for this blog Basic JS Understanding honi chahiye bhai Promises aane chahiye aapko. Promises ke status pata hone chahiye. What we'll study? So, aaj hamlog study karne wale hain Prom

More from this blog

L

Learning Tech

78 posts

A Blog with dozens of articles on different langauges as well as frameworks, This isn't just for you to learn, i crafted it thinking that this could be my future reference as well

It covers Networking, Web Development, Mobile Developement and GenAI too!