TypeScript for JavaScript Developers: The Missing Manual

Published on • 15 min read

If you've been working with JavaScript for any length of time, you've likely encountered the infamous undefined is not a function error. It crashes your app, frustrates your users, and costs you hours of debugging.

Enter TypeScript. Developed by Microsoft, TypeScript is a superset of JavaScript that adds static typing to the language. In 2025, it is no longer just a "nice to have"—it is the industry standard for large-scale web development.

Advertisement

Why the Switch? The Fear of "Any"

Many developers hesitate to switch because they fear the overhead of writing types. "I just want to write code, not defining interfaces!" they say. But consider this scenario:

// JavaScript
function calculateTotal(items) {
  return items.reduce((sum, item) => sum + item.prcie, 0); // Typo! 'prcie'
}
// This code RUNS fine, but returns NaN. You won't know until runtime.

In TypeScript, the compiler stops you before you even save the file:

// TypeScript
interface Item {
  price: number;
  name: string;
}

function calculateTotal(items: Item[]) {
  return items.reduce((sum, item) => sum + item.prcie, 0); 
  // Error: Property 'prcie' does not exist on type 'Item'. Did you mean 'price'?
}

Core Concepts You Must Know

1. Static Typing (The Basics)

You declare what kind of data a variable holds. If you try to assign something else, TypeScript yells at you.

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

2. Interfaces vs Types

This is a common interview question. Generally, they are interchangeable, but Interfaces are better for defining object shapes that might be extended (like in libraries), while Types are more flexible for unions and primitives.

Advertisement

3. Generics: The Power Move

Generics allow you to write reusable code that can work with any data type, while still maintaining strict type safety. Think of them as variables for types.

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

const numbers = wrapper<number>(10); // Returns number[]
const strings = wrapper<string>("Hello"); // Returns string[]

Setting Up a TypeScript Project

Getting started is easier than you think. You can initialize a new TypeScript project in seconds. First, ensure you have Node.js installed, then run:

npm init -y
npm install typescript --save-dev
npx tsc --init

This creates a tsconfig.json file, the heart of any TypeScript project. This file controls how strict the compiler is and what environment it targets (Node, DOM, ES6, etc.).

Advanced Types: Beyond the Basics

Once you master basic types, you unlock the true power of TypeScript. These advanced types allow you to accurately model complex business logic.

Union and Intersection Types

Union types (`|`) allow a variable to be one of multiple types. It's perfect for variables that can accept different formats or specific string literals.

let status: "loading" | "success" | "error";
status = "loading"; // Valid
status = "pending"; // Error: Type '"pending"' is not assignable to type...

Intersection types (`&`) combine multiple types into one. It's incredibly useful for mixing properties from different interfaces into a single, cohesive type.

interface HasName { name: string; }
interface HasAge { age: number; }
type Person = HasName & HasAge;

const user: Person = { name: "Alice", age: 30 }; // Must have both

Utility Types

TypeScript comes with built-in utility types to transform existing types. This prevents you from repeating yourself (DRY) when defining similar structures.

  • Partial<T>: Makes all properties in T optional. Great for update state functions.
  • Pick<T, K>: Plucks specific properties from a broader type.
  • Omit<T, K>: Removes specific properties from a type, useful for hiding sensitive data like passwords from user objects.
interface User {
  id: number;
  email: string;
  name: string;
}

type UserUpdatePayload = Partial<User>; // id?, email?, name?
type UserProfile = Omit<User, "email">; // id, name

Common Pitfalls and "any"

The biggest anti-pattern in TypeScript is overusing the any type. Using any turns off the compiler completely for that variable. It essentially reverts your code back to vanilla JavaScript.

If you don't know the exact type coming from a third-party API, use unknown instead. It forces you to perform type checking (type guards) before interacting with the variable.

Migration Strategy: From JS to TS

You don't have to rewrite your entire codebase overnight. TypeScript allows for incremental adoption:

  1. Rename files: Change `.js` to `.ts` one by one. Or `.jsx` to `.tsx` for React components.
  2. Allow Implicit Any: In your `tsconfig.json`, set `"noImplicitAny": false` initially to suppress errors and allow migration without blocking the build.
  3. Add Types Gradually: Start with strict types for your core data models (API responses), then move outwards to props, state, and utility functions.
Advertisement

TypeScript with Modern Frameworks

If you use React, Vue, or Angular, TypeScript is a game-changer. In React, defining interfaces for your component Props means you get instant documentation and validation whenever you use that component elsewhere in your app. It completely replaces `PropTypes` and does a much better job with zero runtime performance cost.

Conclusion

Writing TypeScript feels slightly slower at first because you have to think about data structures upfront. However, it makes you incredibly fast in the long run. The autocomplete (IntelliSense) alone is worth the price of admission. It documents your code as you write it, making it easier for your future self, and your team, to understand what a generic `userObject` actually contains.

"TypeScript doesn't prevent all bugs. It prevents you from writing code that you didn't intend to write, catching typos and logic errors before they ever hit the browser."