Skip to main content

TypeScript for JavaScript Developers - Complete Guide

By · · 4 min read

TypeScript Programming

TypeScript for JavaScript Developers: Complete Mastery Guide

Introduction

I was skeptical about TypeScript. Seemed like busy work. JavaScript worked fine, why add types?

Then I joined a team that used TypeScript. The autocomplete was insane. I typed something wrong and the editor told me immediately. No more debugging runtime type errors.

Built a small project in TS. Never went back.

The first week felt slow. Declaring types seemed tedious. But after that, development actually sped up. Refactoring was safe because TypeScript caught breaking changes. Code was self-documenting.

This guide covers everything useful about TypeScript. Not the boring stuff—real patterns you'll use.


Why TypeScript?

Real Problems It Solves

// JavaScript - bugs found at runtime ❌
function add(a, b) {
  return a + b;
}
add("5", 3);  // "53" - oops!

// TypeScript - bugs found at compile time ✓
function add(a: number, b: number): number {
  return a + b;
}
add("5", 3);  // Error: Argument of type 'string' is not assignable

Benefits:

  • Catch errors before runtime
  • Better IDE autocomplete
  • Self-documenting code
  • Easier refactoring
  • Improved team collaboration

TypeScript Basics

Primitive Types

// Basic types
let str: string = "hello";
let num: number = 42;
let bool: boolean = true;
let any_value: any = "anything";  // Avoid when possible
let undefined_val: undefined = undefined;
let null_val: null = null;

Arrays and Tuples

// Arrays
let numbers: number[] = [1, 2, 3];
let strings: Array<string> = ["a", "b"];

// Tuples (fixed length, specific types)
let tuple: [string, number] = ["hello", 42];
let tuple2: [string, number, boolean] = ["ok", 1, true];

Union and Intersection Types

// Union - value can be one of these types
type Status = "success" | "error" | "loading";
let status: Status = "success";  // ✓
let status2: Status = "pending";  // ✗ Error

// Intersection - combine multiple types
type Admin = User & { role: "admin" };
type Moderator = User & { role: "moderator" };

Interfaces and Types

Interfaces for Objects

// Define object shape
interface User {
  id: number;
  name: string;
  email: string;
  age?: number;  // Optional property
  readonly createdAt: Date;  // Read-only
}

// Usage
const user: User = {
  id: 1,
  name: "John",
  email: "john@example.com",
  createdAt: new Date()
};

Extending Interfaces

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

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

const admin: Admin = {
  id: 1,
  name: "Admin",
  permissions: ["read", "write", "delete"]
};

Interface vs Type

// Both can describe objects
interface Point {
  x: number;
  y: number;
}

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

// Key differences:
// - Interfaces can be merged (same name = extends)
// - Types can use Union, Intersection
// - Interfaces are more extensible for objects
// - Types are better for unions and complex types

Functions with TypeScript

Function Types

// Defining function signature
type AddFn = (a: number, b: number) => number;
const add: AddFn = (a, b) => a + b;

// Function declaration
function multiply(a: number, b: number): number {
  return a * b;
}

// Optional parameters
function greet(name: string, greeting?: string): string {
  return `${greeting || "Hello"}, ${name}`;
}

// Default parameters
function process(data: string, format: string = "json"): void {
  console.log(`Processing ${data} as ${format}`);
}

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

Generics (Power of TypeScript)

Generic Functions

// Without generics (loses type info)
function first(arr: any[]): any {
  return arr[0];
}

// With generics (preserves type)
function first<T>(arr: T[]): T {
  return arr[0];
}

const num = first([1, 2, 3]);      // TypeScript knows it's number
const str = first(["a", "b"]);     // TypeScript knows it's string

Generic Interfaces

interface Container<T> {
  value: T;
  getValue(): T;
  setValue(val: T): void;
}

interface NumberContainer extends Container<number> {
  // value: number;
  // getValue(): number;
}

const container: Container<string> = {
  value: "hello",
  getValue() { return this.value; },
  setValue(val) { this.value = val; }
};

Generic Constraints

// T must have length property
function getLength<T extends { length: number }>(obj: T): number {
  return obj.length;
}

getLength("hello");     // ✓
getLength([1, 2, 3]);   // ✓
getLength(42);          // ✗ Error

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

const user = { name: "John", age: 30 };
const name = getProperty(user, "name");   // ✓ string
const age = getProperty(user, "age");     // ✓ number
const email = getProperty(user, "email"); // ✗ Error

Advanced TypeScript Patterns

Conditional Types

// Type depends on another type
type IsString<T> = T extends string ? true : false;

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

// Practical example
type Flatten<T> = T extends Array<infer U> ? U : T;

type Str = Flatten<string[]>;     // string
type Num = Flatten<number>;        // number

Mapped Types

// Transform each property of a type
interface User {
  name: string;
  age: number;
}

// Make all properties readonly
type ReadonlyUser = {
  readonly [K in keyof User]: User[K];
};

// Make all properties optional
type PartialUser = {
  [K in keyof User]?: User[K];
};

// Make all properties strings
type Stringify<T> = {
  [K in keyof T]: string;
};

Decorators

// Function decorator
function timing(target: any, key: string, descriptor: PropertyDescriptor) {
  const original = descriptor.value;
  descriptor.value = function(...args: any[]) {
    console.time(key);
    const result = original.apply(this, args);
    console.timeEnd(key);
    return result;
  };
  return descriptor;
}

// Class decorator
function sealed(constructor: Function) {
  Object.seal(constructor);
  Object.seal(constructor.prototype);
}

@sealed
class User {
  @timing
  calculateAge(): number {
    return new Date().getFullYear() - 1990;
  }
}

Real-World Best Practices

1. Use Strict Mode

{
  "compilerOptions": {
    "strict": true,
    "strictNullChecks": true,
    "strictFunctionTypes": true
  }
}

2. Null Safety

interface User {
  name: string;
  email?: string;  // Optional = string | undefined
}

// Null coalescing operator
const email = user.email ?? "no-email";

// Optional chaining
const domain = user.email?.split("@")[1];

3. Type Guards

function process(value: string | number) {
  if (typeof value === "string") {
    // TypeScript knows it's string here
    return value.toUpperCase();
  } else {
    // TypeScript knows it's number here
    return value.toFixed(2);
  }
}

Common Mistakes to Avoid

  1. Using any too much - Defeats the purpose of TypeScript
  2. Not using strict mode - Misses many errors
  3. Ignoring compiler errors - They're there to help!
  4. Over-engineering types - Keep it simple
  5. Not versioning node_modules/@types - Can cause breaking changes

TypeScript with React

Props Typing

interface ButtonProps {
  onClick: () => void;
  children: React.ReactNode;
  disabled?: boolean;
}

const Button: React.FC<ButtonProps> = ({ onClick, children, disabled }) => (
  <button onClick={onClick} disabled={disabled}>
    {children}
  </button>
);

Hooks Typing

const [count, setCount] = useState<number>(0);
const [user, setUser] = useState<User | null>(null);

const handleClick: React.MouseEventHandler<HTMLButtonElement> = (e) => {
  console.log(e.currentTarget);
};

Tools and Resources

  • TypeScript Playground: https://www.typescriptlang.org/play
  • Type Definitions: DefinitelyTyped for popular libraries
  • Learning: TypeScript Handbook, Total TypeScript course
  • Utilities: ts-node for running TS directly

Conclusion

TypeScript takes discipline initially but pays dividends in code quality, maintainability, and developer confidence. Start with basics, gradually adopt advanced patterns, and soon you'll wonder how you ever wrote JavaScript without types!

Type safety is not about being verbose—it's about catching bugs early! 🎯