TypeScript for JavaScript Developers: Complete Mastery Guide
Introduction
TypeScript adds type safety to JavaScript, preventing bugs before they reach production. What started as a Microsoft experiment has become industry standard at companies like Google, Microsoft, Airbnb, and Slack. This guide takes JavaScript developers from TypeScript basics to advanced patterns, with practical examples you'll use daily.
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 assignableBenefits:
- 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 typesFunctions 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 stringGeneric 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"); // ✗ ErrorAdvanced 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>; // numberMapped 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
- Using
anytoo much - Defeats the purpose of TypeScript - Not using strict mode - Misses many errors
- Ignoring compiler errors - They're there to help!
- Over-engineering types - Keep it simple
- 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! 🎯