runtime.boot

Full Stack Systems
Production Mindset

CodeWithMihir

Engineering thoughtful products from interface to infrastructure.

CodeWithMihir

TypeScript Tutorial

Why TypeScript: Problems TypeScript Solves in JavaScript Projects

Understand why TypeScript is useful by exploring common JavaScript bugs, safer refactoring, better editor support, API contracts, and maintainable project structure.

Welcome back! I am Mihir, and in the previous lesson we learned what TypeScript is.

Now let us answer a very practical question:

Why should we use TypeScript when JavaScript already works?

This is a fair question. JavaScript is powerful, popular, and runs everywhere. TypeScript is not popular because JavaScript is bad. TypeScript is popular because modern apps are bigger, more connected, and harder to maintain without extra safety.


The Main Problem: JavaScript Finds Many Bugs Too Late

JavaScript usually finds type-related bugs when the code is running.

That means bugs can appear:

  • During testing
  • After deployment
  • When a user enters unexpected data
  • When an API response changes
  • When another developer uses your function incorrectly

Example:

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

console.log(getDiscountedPrice(1000, 0.1));      // 900
console.log(getDiscountedPrice("1000", 0.1));    // 900
console.log(getDiscountedPrice("free", 0.1));    // NaN

The function looks simple, but it accepts anything.

Now see the TypeScript version:

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

getDiscountedPrice(1000, 0.1);
getDiscountedPrice("free", 0.1);

TypeScript warns you:

Argument of type 'string' is not assignable to parameter of type 'number'.

This is the first big reason to use TypeScript:

It moves many bugs from runtime to development time.


Problem 1: Wrong Function Arguments

In JavaScript, functions do not clearly tell you what they expect unless you read the implementation or documentation.

function createUser(name, age, isAdmin) {
  return {
    name,
    age,
    isAdmin,
  };
}

const user = createUser("Mihir", true, 25);

This code runs, but the arguments are in the wrong order:

  • age receives true
  • isAdmin receives 25

TypeScript catches this immediately:

function createUser(name: string, age: number, isAdmin: boolean) {
  return {
    name,
    age,
    isAdmin,
  };
}

const user = createUser("Mihir", true, 25);

TypeScript tells us:

Argument of type 'boolean' is not assignable to parameter of type 'number'.

Better Real-World Pattern

For functions with multiple values, an object is often clearer.

type CreateUserInput = {
  name: string;
  age: number;
  isAdmin: boolean;
};

function createUser(input: CreateUserInput) {
  return {
    name: input.name,
    age: input.age,
    isAdmin: input.isAdmin,
  };
}

const user = createUser({
  name: "Mihir",
  age: 25,
  isAdmin: false,
});

This is easier to read because every value has a name.


Problem 2: Missing Object Properties

JavaScript objects are flexible, but that flexibility can hide mistakes.

const product = {
  id: 1,
  title: "Laptop",
  price: 75000,
};

function printProduct(product) {
  console.log(product.name.toUpperCase());
}

printProduct(product);

This crashes because product.name does not exist.

TypeError: Cannot read properties of undefined

In TypeScript:

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

const product: Product = {
  id: 1,
  title: "Laptop",
  price: 75000,
};

function printProduct(product: Product) {
  console.log(product.name.toUpperCase());
}

TypeScript warns:

Property 'name' does not exist on type 'Product'.

This is powerful because TypeScript understands the object structure before the code runs.


Problem 3: Unsafe API Responses

In real apps, you often fetch data from an API.

JavaScript version:

async function getUser() {
  const response = await fetch("/api/user");
  const user = await response.json();

  console.log(user.profile.name);
}

This assumes the API response always has:

user.profile.name

But what if the API sends:

{
  "id": 1,
  "name": "Mihir"
}

Then user.profile is undefined, and the code crashes.

TypeScript lets us describe what we expect:

type UserResponse = {
  id: number;
  name: string;
  profile?: {
    bio: string;
    avatarUrl: string;
  };
};

async function getUser(): Promise<UserResponse> {
  const response = await fetch("/api/user");
  return response.json();
}

async function showUser() {
  const user = await getUser();

  console.log(user.name);
  console.log(user.profile?.bio);
}

The ? in profile? means the property is optional.

So TypeScript encourages safe access:

user.profile?.bio

instead of unsafe access:

user.profile.bio

Problem 4: Refactoring Becomes Risky

Refactoring means changing code structure without changing behavior.

Example: you rename a property from username to displayName.

In JavaScript:

const user = {
  username: "Mihir",
};

console.log(user.username);

After refactoring:

const user = {
  displayName: "Mihir",
};

console.log(user.username);

This code does not show an error immediately. It logs undefined.

In TypeScript:

type User = {
  displayName: string;
};

const user: User = {
  displayName: "Mihir",
};

console.log(user.username);

TypeScript warns:

Property 'username' does not exist on type 'User'.

This makes refactoring much safer.

In a large project, this is a huge benefit. You can rename types, functions, and properties with more confidence.


Problem 5: Code Is Harder to Understand Later

Imagine you find this function in a project:

function sendEmail(user, options) {
  // many lines of code
}

Questions immediately come up:

  • What should user contain?
  • Is options required?
  • What fields exist inside options?
  • Does the function return anything?

Now compare the TypeScript version:

type EmailUser = {
  email: string;
  name: string;
};

type EmailOptions = {
  subject: string;
  template: "welcome" | "reset-password" | "invoice";
  sendCopy?: boolean;
};

function sendEmail(user: EmailUser, options: EmailOptions): boolean {
  console.log(`Sending ${options.template} email to ${user.email}`);
  return true;
}

Without reading the whole function, you already know:

  • user.email must exist
  • user.name must exist
  • subject is required
  • template can only be one of three values
  • sendCopy is optional
  • the function returns a boolean

TypeScript makes code self-documenting.


Problem 6: Invalid States in Your App

Many bugs happen because your code allows states that should not be possible.

JavaScript example:

const requestState = {
  loading: true,
  success: true,
  error: "Something went wrong",
  data: { id: 1 },
};

This object is confusing.

Is the request loading? Did it succeed? Did it fail?

TypeScript lets us model valid states clearly:

type RequestState =
  | { status: "idle" }
  | { status: "loading" }
  | { status: "success"; data: { id: number; name: string } }
  | { status: "error"; message: string };

Now each state has only the data it needs.

const state: RequestState = {
  status: "success",
  data: {
    id: 1,
    name: "Mihir",
  },
};

And when we use it:

function renderState(state: RequestState) {
  if (state.status === "loading") {
    return "Loading...";
  }

  if (state.status === "error") {
    return state.message;
  }

  if (state.status === "success") {
    return state.data.name;
  }

  return "Start request";
}

TypeScript understands which properties are available in each state.

This is one of the most practical TypeScript skills for frontend apps.


TypeScript Is Especially Useful In These Places

TypeScript gives the biggest benefit when your project has:

  • Many files
  • Many developers
  • Complex API responses
  • Shared reusable components
  • Business logic
  • Forms
  • Authentication
  • Dashboards
  • Payment flows
  • Admin panels
  • Long-term maintenance

For a tiny one-file script, JavaScript may be enough.

For a serious app, TypeScript becomes very valuable.


Does TypeScript Prevent Every Bug?

No.

TypeScript catches type-related mistakes, but it does not automatically solve every problem.

It will not automatically catch:

  • Wrong business logic
  • Bad UI behavior
  • Wrong API data at runtime unless you validate it
  • Security problems
  • Poor performance
  • Incorrect algorithms

Example:

function calculateDiscount(price: number): number {
  return price + 100;
}

TypeScript is happy because the types are correct.

But the logic is wrong because a discount should probably reduce the price.

So remember:

TypeScript improves safety, but you still need good thinking, testing, and clean architecture.


TypeScript Benefits Summary

BenefitWhy It Matters
Early error detectionCatch mistakes before running code
Better autocompleteWrite code faster with editor help
Safer refactoringRename and restructure with confidence
Clear contractsFunctions and objects show what they expect
Better teamworkOther developers understand your code faster
Fewer runtime crashesMany common undefined/type bugs are avoided
Easier scalingLarge codebases stay more manageable

Practice: Convert JavaScript Thinking to TypeScript Thinking

JavaScript

function calculateInvoice(customer, amount, paid) {
  return {
    customer,
    amount,
    paid,
  };
}

TypeScript

type Invoice = {
  customer: string;
  amount: number;
  paid: boolean;
};

function calculateInvoice(
  customer: string,
  amount: number,
  paid: boolean
): Invoice {
  return {
    customer,
    amount,
    paid,
  };
}

Better Version

type CreateInvoiceInput = {
  customer: string;
  amount: number;
  paid: boolean;
};

type Invoice = {
  customer: string;
  amount: number;
  paid: boolean;
  createdAt: Date;
};

function createInvoice(input: CreateInvoiceInput): Invoice {
  return {
    customer: input.customer,
    amount: input.amount,
    paid: input.paid,
    createdAt: new Date(),
  };
}

This version is easier to extend because all input values are grouped in one object.


What You've Learned

You now understand why TypeScript is useful:

  • JavaScript often catches type bugs too late
  • TypeScript catches many mistakes while coding
  • Function arguments become safer
  • Object properties become clearer
  • API responses can be described with types
  • Refactoring becomes less scary
  • Code becomes easier to understand
  • TypeScript helps model valid app states

What's Next?

In the next lesson, we will compare TypeScript vs JavaScript directly.

We will look at syntax, type checking, compilation, learning curve, tooling, and when to choose each one.


Need Help?

  • Have questions, confusion, or want to know more? Contact me

The goal is not to write more complicated code. The goal is to make your JavaScript code safer, clearer, and easier to maintain.