TypeScript Basics
Learn the fundamentals of TypeScript and how it enhances JavaScript with type safety.
A Simple Analogy
Imagine a letter carrier delivering mail. In JavaScript (regular mail), anyone can write anything on the envelope, and sometimes it gets lost or sent to the wrong place. In TypeScript (certified mail), you specify exactly who the letter is for, what's inside, and how heavy it is. The postal service (compiler) checks everything before delivery and warns you if something looks wrong.
What Is TypeScript?
TypeScript is a programming language built on top of JavaScript that adds static typing. It allows you to declare what type (string, number, boolean, custom objects) a variable should be, and the compiler checks for errors before you run your code.
Why Use TypeScript?
- Catch errors early: Type mismatches are caught during development, not in production
- Better IDE support: Your editor can autocomplete and warn about mistakes
- Self-documenting code: Types show what a function expects and returns
- Refactoring confidence: Change code safely knowing the compiler will catch breaking changes
- Scalability: Large projects stay maintainable with clear type contracts
- Better collaboration: Teams understand what data each function needs
Basic Types
Primitive Types
// String
const name: string = "Alice";
// Number
const age: number = 30;
const price: number = 19.99;
// Boolean
const isActive: boolean = true;
// Undefined and Null
const nothing: undefined = undefined;
const empty: null = null;
// Any (avoid when possible - defeats the purpose!)
const mystery: any = "could be anything";
Arrays
// Array of strings
const names: string[] = ["Alice", "Bob"];
// Array of numbers
const scores: number[] = [95, 87, 92];
// Alternative syntax
const colors: Array<string> = ["red", "blue"];
// Mixed (if needed)
const mixed: (string | number)[] = ["hello", 42];
Union Types (Multiple Options)
// A variable can be one of several types
const id: string | number = 123; // Can be string or number
const result: string | null = getValue(); // Might be null
Literal Types (Specific Values)
// Only these exact values allowed
type Status = "pending" | "active" | "inactive";
const userStatus: Status = "active"; // ✓ OK
// userStatus = "unknown"; // ✗ Error
Interfaces & Types
Interfaces (Describing Objects)
// Define what a User object should look like
interface User {
id: number;
name: string;
email: string;
isActive?: boolean; // Optional (with ?)
}
// Now you can use it
const user: User = {
id: 1,
name: "Alice",
email: "alice@example.com",
isActive: true,
};
Type Aliases
// Similar to interface, but more flexible
type Address = {
street: string;
city: string;
zip: string;
};
const address: Address = {
street: "123 Main St",
city: "Portland",
zip: "97201",
};
Differences (Simplified)
// Interface: better for object shapes, extendable
interface Animal {
name: string;
}
interface Dog extends Animal {
breed: string;
}
// Type: more flexible, supports unions
type Size = "small" | "medium" | "large";
Functions with Types
Basic Function Types
// Parameters and return type
function greet(name: string): string {
return `Hello, ${name}!`;
}
// Arrow function
const add = (a: number, b: number): number => a + b;
// Function with optional parameter
function describe(name: string, age?: number): string {
return age ? `${name} is ${age}` : `${name}'s age unknown`;
}
// Function with default value
function setDelay(ms: number = 1000): void {
setTimeout(() => {}, ms);
}
Function with Complex Types
interface OrderItem {
productId: number;
quantity: number;
price: number;
}
function calculateOrderTotal(items: OrderItem[]): number {
return items.reduce((sum, item) => sum + item.price * item.quantity, 0);
}
// Usage
const items: OrderItem[] = [
{ productId: 1, quantity: 2, price: 10 },
{ productId: 2, quantity: 1, price: 20 },
];
const total = calculateOrderTotal(items); // ✓ Type-safe
Classes with Types
class User {
id: number;
name: string;
email: string;
constructor(id: number, name: string, email: string) {
this.id = id;
this.name = name;
this.email = email;
}
getDisplayName(): string {
return `${this.name} (${this.email})`;
}
}
const user = new User(1, "Alice", "alice@example.com");
console.log(user.getDisplayName()); // "Alice (alice@example.com)"
Access Modifiers
class Account {
public balance: number; // Anyone can access
private pin: number; // Only class can access
protected accountType: string; // Class and subclasses can access
constructor(balance: number, pin: number) {
this.balance = balance;
this.pin = pin;
}
withdraw(amount: number): void {
if (amount <= this.balance) {
this.balance -= amount;
}
}
}
Generics (Reusable Types)
// Generic function - works with any type
function getFirstElement<T>(array: T[]): T | undefined {
return array[0];
}
const firstString = getFirstElement(["a", "b"]); // Type: string
const firstNumber = getFirstElement([1, 2, 3]); // Type: number
// Generic interface
interface Container<T> {
value: T;
getValue(): T;
}
const stringContainer: Container<string> = {
value: "hello",
getValue() { return this.value; }
};
const numberContainer: Container<number> = {
value: 42,
getValue() { return this.value; }
};
Practical Examples
E-commerce Product System
// Define types
interface Product {
id: number;
name: string;
price: number;
inStock: boolean;
}
interface CartItem {
product: Product;
quantity: number;
}
interface Cart {
items: CartItem[];
getTotal(): number;
}
// Implement with type safety
class ShoppingCart implements Cart {
items: CartItem[] = [];
addItem(product: Product, quantity: number): void {
const existing = this.items.find(item => item.product.id === product.id);
if (existing) {
existing.quantity += quantity;
} else {
this.items.push({ product, quantity });
}
}
getTotal(): number {
return this.items.reduce(
(sum, item) => sum + item.product.price * item.quantity,
0
);
}
}
// Usage with full type safety
const cart = new ShoppingCart();
const laptop: Product = { id: 1, name: "Laptop", price: 999, inStock: true };
cart.addItem(laptop, 1);
console.log(cart.getTotal()); // 999
API Response Handling
interface ApiResponse<T> {
success: boolean;
data?: T;
error?: string;
}
async function fetchUser(id: number): Promise<ApiResponse<User>> {
try {
const response = await fetch(`/api/users/${id}`);
const data = await response.json();
return { success: true, data };
} catch (error) {
return { success: false, error: error.message };
}
}
// Type-safe usage
const response = await fetchUser(1);
if (response.success && response.data) {
console.log(response.data.name); // ✓ TypeScript knows data is a User
}
Real-World Use Cases
- Web applications: React, Vue, Angular with full type safety
- Node.js backends: Express, NestJS with typed APIs
- Full-stack development: Share types between frontend and backend
- Large teams: Types serve as contracts, preventing miscommunication
- Refactoring: Compiler catches breaking changes across the codebase
Best Practices
- Use
string,number,booleanfor primitive types - Define interfaces for object shapes
- Use unions for values that can be multiple types
- Add
readonlyfor immutable properties - Use generics for reusable, type-safe components
- Avoid
any(useunknownif you must) - Enable strict mode in
tsconfig.json
Common Pitfalls
Using any (Defeats the Purpose)
// Bad
const user: any = someData;
// Good
const user: User = someData;
Over-Complicating Types
// Too complex
type ComplexType = string | number | boolean | object | Array<any>;
// Better
type Value = string | number;
Forgetting Type Imports
// This might cause issues at runtime
import User from "./User"; // Is this a type or class?
// Better - explicit
import type { User } from "./User"; // Type only
import { UserClass } from "./User"; // Runtime value
TypeScript Configuration
Key tsconfig.json Settings
{
"compilerOptions": {
"target": "ES2020",
"lib": ["ES2020", "DOM"],
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true
}
}
Related Concepts to Explore
- Type guards and type narrowing
- Decorators and metadata
- Modules and namespaces
- Advanced generics and constraints
- Conditional types
- Mapped types
- Type inference
- JSDoc type annotations
- Flow (alternative to TypeScript)
- Type testing with libraries like tsd
Summary
TypeScript transforms JavaScript from a loosely-typed language into one with strong type safety. By declaring what types your variables, functions, and objects should be, you catch mistakes early, get better IDE support, and write code that's self-documenting. Start with basic types, graduate to interfaces and generics, and you'll build more reliable, maintainable applications that scale from hobby projects to enterprise systems.