TypeScript
TypeScript is a superset of JavaScript that adds static typing. It compiles down to plain JavaScript that runs in any browser or Node.js environment.It is not a language it just helps you to find type-errors
in advance.
Core Features
Static Typing
// Variables with typeslet name: string = "Alice";let age: number = 30;let active: boolean = true;
// Function with typed parameters and return typefunction add(a: number, b: number): number { return a + b;}
Interfaces
interface User { id: number; name: string; email?: string; // Optional property}
function greetUser(user: User) { console.log(`Hello, ${user.name}!`);}
Type Inference
TypeScript can automatically detect types:
// No type annotations needed (let name: string = "Alice";)let name = "Alice"; // TypeScript knows this is a stringlet numbers = [1, 2, 3]; // TypeScript knows this is number[]
Common Misconceptions
-
TypeScript is a different language from JavaScript
- Reality: TypeScript is a superset of JavaScript.
-
TypeScript improves runtime performance
- Reality: TypeScript only helps during development. The compiled JavaScript has identical performance.
-
TypeScript guarantees type safety
- Reality: TypeScript has escape hatches (
any
, type assertions) and can’t catch all errors.
- Reality: TypeScript has escape hatches (
When to Use TypeScript
- When building larger applications
- When working with a team
- When you want better tooling and IDE support
- When maintaining code long-term
TypeScript vs JavaScript
Feature | TypeScript | JavaScript |
---|---|---|
Static typing | ✅ | ❌ |
Interfaces | ✅ | ❌ |
Generics | ✅ | ❌ |
Browser support | Requires compilation | Native |
Installation
npm install -g typescript // globallynpx tsc // locally
Compilation
tsc filename.ts
- Do not use node to run typescript file, use tsc to compile the file into javascript and then run the compiled file using node.
Typescript Configuration
tsc --init
- This will create a
tsconfig.json
file in the root of your project. - This file contains the configuration for the TypeScript compiler.
outDir
is the directory where the compiled JavaScript files will be placed.rootDir
is the directory where the TypeScript source files are located.target
is the version of JavaScript that the TypeScript compiler will compile to.module
is the module code generation.strict
is a flag to enable all strict type-checking options.
Any Type
The any
type lets you opt-out of type checking:
let anything: any;anything = "Hello";anything = 1;anything = true;anything.nonExistentMethod(); // No error during compilation
Better Alternatives
// Instead of any, use more specific types:let value: unknown; // Similar to any but saferlet id: string | number; // Union typelet callback: () => void; // Function type
TypeScript’s goal is to avoid any
when possible. Use more specific types for better code quality.
Functions in TypeScript
Functions can have typed parameters and return values:
// Basic function with typesfunction greet(name: string): string { return `Hello, ${name}!`;}
// Arrow function with typesconst multiply = (a: number, b: number): number => a * b;
// Optional parameters (? makes it optional)function createUser(name: string, age?: number): object { return age ? { name, age } : { name };}
// Default parametersfunction increment(value: number, amount: number = 1): number { return value + amount;}
// Rest parametersfunction sum(...numbers: number[]): number { return numbers.reduce((total, n) => total + n, 0);}
// Function type definitiontype MathOperation = (x: number, y: number) => number;const add: MathOperation = (a, b) => a + b;
Function Overloads
Function overloads allow a function to be called in multiple ways:
// Overload signaturesfunction process(x: number): number;function process(x: string): string;// Implementationfunction process(x: number | string): number | string { if (typeof x === "number") { return x * 2; } else { return `${x} processed`; }}
const a = process(123); // Returns a numberconst b = process("hello"); // Returns a string
Never Type
The never
type represents values that never occur:
// Function that never returns (throws)function throwError(message: string): never { throw new Error(message);}
// Function with unreachable end pointfunction infiniteLoop(): never { while (true) { // do something forever }}
The never
type is useful for:
- Functions that always throw exceptions
- Functions with infinite loops
Objects in TypeScript
// Basic object typelet user: { name: string; age: number } = { name: "Alice", age: 30};
// Type alias for reusetype User = { id: number; name: string; email?: string; // Optional property readonly createdAt: Date; // Can't be modified};
// Index signature for dynamic propertiestype Dictionary = { [key: string]: string;};
const colors: Dictionary = { red: "#ff0000", green: "#00ff00"};
// Nested objectstype Customer = { name: string; address: { city: string; zipCode: string; }};
Type Aliases
// Basic type aliastype Point = { x: number; y: number;};
// Union type aliastype Status = 'pending' | 'success' | 'error';
// Function type aliastype Calculator = (x: number, y: number) => number;
// Combining type aliasestype Circle = { kind: 'circle'; radius: number;};
type Square = { kind: 'square'; size: number;};
type Shape = Circle | Square;
Type aliases help you:
- Create reusable types
- Make code more readable
- Define union and intersection types
- Create complex type definitions
Combining Types
// Union Types (OR)type ID = string | number;type Status = 'loading' | 'success' | 'error';
// Intersection Types (AND)type Employee = { id: number; name: string;};
type Address = { city: string; country: string;};
type EmployeeWithAddress = Employee & Address;
// Extending Typestype Animal = { name: string;};
type Dog = Animal & { breed: string;};
// Generic Typestype List<T> = { items: T[]; total: number;};
const numberList: List<number> = { items: [1, 2, 3], total: 3};
Arrays in TypeScript
// Basic array typeslet numbers: number[] = [1, 2, 3];let names: string[] = ["Alice", "Bob"];
// Alternative syntaxlet items: Array<number> = [1, 2, 3];
// Mixed types arraylet mixed: (string | number)[] = [1, "two", 3];
// Array of objectstype User = { id: number; name: string };let users: User[] = [ { id: 1, name: "Alice" }, { id: 2, name: "Bob" }];
// Readonly arraysconst readOnlyNumbers: ReadonlyArray<number> = [1, 2, 3];// readOnlyNumbers.push(4); // Error: Property 'push' does not exist
// Tuple (fixed-length array)let tuple: [string, number] = ["age", 25];
Tuples in TypeScript
Tuples are fixed-length arrays with elements of different types. They are useful for:
- Returning multiple values from functions
- Working with fixed-length, mixed-type data
// Basic tuplelet person: [string, number] = ["Alice", 25];
// Optional elementslet record: [string, number?] = ["Alice"]; // Second element optional
// Rest elementslet team: [string, ...number[]] = ["Scores", 90, 85, 75];
// Readonly tupleconst point: readonly [number, number] = [10, 20];// point[0] = 30; // Error: Cannot assign to '0' because it is a read-only property
Enums in TypeScript
Enums allow you to define a set of named constants. They make it easier to handle fixed sets of values.
// Numeric enumenum Direction { Up, // 0 Down, // 1 Left, // 2 Right // 3}
// String enumenum Color { Red = "RED", Green = "GREEN", Blue = "BLUE"}
// Usagelet move: Direction = Direction.Up;let color: Color = Color.Red;
// Const enum (better performance)const enum Status { Active = "ACTIVE", Inactive = "INACTIVE"}
// Use when you have a fixed set of optionsfunction setStatus(status: Status) { // ...}
// ⚠️ Avoid number enums unless you really need them// ✅ Prefer string enums or union types for better type safetytype UserRole = "admin" | "user" | "guest"; // Union type alternative
Classes in TypeScript
// Basic classclass Person { // Properties name: string; private age: number; protected role: string; readonly id: number;
// Constructor constructor(name: string, age: number) { this.name = name; this.age = age; this.id = Math.random(); }
// Methods greet(): string { return `Hello, I'm ${this.name}`; }}
// Inheritanceclass Employee extends Person { constructor( name: string, age: number, private department: string ) { super(name, age); }
// Getter get employeeInfo(): string { return `${this.name} works in ${this.department}`; }
// Setter set setDepartment(newDepartment: string) { if (newDepartment.length > 0) { this.department = newDepartment; } else { throw new Error("Department name cannot be empty"); } }}
// Interface implementationinterface Vehicle { start(): void; stop(): void;}
class Car implements Vehicle { start() { console.log('Starting...'); } stop() { console.log('Stopping...'); }}
// Abstract classabstract class Shape { abstract getArea(): number;
printArea(): void { console.log(this.getArea()); }}
class Circle extends Shape { constructor(private radius: number) { super(); }
getArea(): number { return Math.PI * this.radius ** 2; }}
// Interface with Classes Examplesinterface Database { connect(): void; query(sql: string): any[]; close(): void;}
// Multiple classes implementing same interfaceclass MySQLDatabase implements Database { connect() { console.log('MySQL connected'); } query(sql: string) { return []; } close() { console.log('MySQL closed'); }}
class PostgresDatabase implements Database { connect() { console.log('Postgres connected'); } query(sql: string) { return []; } close() { console.log('Postgres closed'); }}
// Interface inheritanceinterface UserService { findById(id: number): User;}
interface AdminService extends UserService { deleteUser(id: number): void;}
// Class implementing multiple interfacesinterface Logger { log(message: string): void;}
class UserManager implements UserService, Logger { findById(id: number): User { return { id, name: "User" }; } log(message: string) { console.log(message); }}
// Usage examplefunction processDatabase(db: Database) { db.connect(); const results = db.query("SELECT * FROM users"); db.close(); return results;}
Key features:
- Access modifiers:
public
,private
,protected
readonly
properties- Inheritance with
extends
- Interface implementation
- Abstract classes
- Getters and setters
Key interface use cases:
- Defining contracts for different implementations
- Creating pluggable components
- Ensuring consistent method signatures
- Supporting dependency injection
- Enabling code reusability
Abstract Classes
Abstract classes are special classes that:
- Cannot be instantiated directly means you cannot create an instance of an abstract class.
- Must be inherited by other classes
- Force child classes to implement abstract methods
// Abstract Class Exampleabstract class Animal { // Can have constructor constructor(protected name: string) {}
// Can have implemented methods makeSound(): void { console.log(`${this.name} makes a sound`); }
// Can have private members private age: number = 0;
// Abstract method that must be implemented abstract move(): void;}
// Concrete class implementing Abstract classclass Dog extends Animal { // Must extend only one abstract class constructor(name: string) { super(name); }
// Must implement abstract methods move(): void { console.log(`${this.name} walks on four legs`); }}
// Interface Exampleinterface Vehicle { // Only method signatures, no implementation start(): void; stop(): void;}
interface Electric { charge(): void;}
// Class can implement multiple interfacesclass Tesla implements Vehicle, Electric { // Must implement all interface methods start(): void { console.log("Tesla starts silently"); }
stop(): void { console.log("Tesla stops"); }
charge(): void { console.log("Tesla is charging"); }}
// Usageconst dog = new Dog("Rex");dog.makeSound(); // "Rex makes a sound"dog.move(); // "Rex walks on four legs"
const tesla = new Tesla();tesla.start(); // "Tesla starts silently"tesla.charge(); // "Tesla is charging"
Abstract Classes vs Interfaces
-
Abstract Classes:
- Can have implemented methods
- Can have constructor
- Can have private/protected members
- Class can extend only one abstract class
-
Interfaces:
- Only method signatures, no implementation
- No constructor
- Only public members
- Class can implement multiple interfaces
Generics in TypeScript
Generics let you write flexible, reusable code that works with any type while maintaining type safety.
// Simple Generic Functionfunction getFirst<Type>(items: Type[]): Type { return items[0];}
// Using the functiongetFirst([1, 2, 3]); // Returns numbergetFirst(["a", "b", "c"]); // Returns stringgetFirst([true, false]); // Returns boolean
// Generic Class Exampleclass Box<Type> { private content: Type;
constructor(value: Type) { this.content = value; }
get(): Type { return this.content; }}
// Using the Box classconst numberBox = new Box(123); // Box<number>const stringBox = new Box("hello"); // Box<string>
// Generic with Multiple Typesfunction pair<Type1, Type2>(first: Type1, second: Type2) { return { first, second };}
// Using pair functionpair("name", 100); // { first: string, second: number }pair(true, 42); // { first: boolean, second: number }
// Generic Constraint Exampleinterface HasName { name: string;}
function printName<Type extends HasName>(item: Type) { console.log(item.name);}
// Using constrained genericprintName({ name: "John", age: 30 }); // Works// printName({ age: 30 }); // Error: missing name property