Skip to content

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 types
let name: string = "Alice";
let age: number = 30;
let active: boolean = true;
// Function with typed parameters and return type
function 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 string
let numbers = [1, 2, 3]; // TypeScript knows this is number[]

Common Misconceptions

  1. TypeScript is a different language from JavaScript

    • Reality: TypeScript is a superset of JavaScript.
  2. TypeScript improves runtime performance

    • Reality: TypeScript only helps during development. The compiled JavaScript has identical performance.
  3. TypeScript guarantees type safety

    • Reality: TypeScript has escape hatches (any, type assertions) and can’t catch all errors.

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

FeatureTypeScriptJavaScript
Static typing
Interfaces
Generics
Browser supportRequires compilationNative

Installation

Terminal window
npm install -g typescript // globally
npx tsc // locally

Compilation

Terminal window
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 safer
let id: string | number; // Union type
let 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 types
function greet(name: string): string {
return `Hello, ${name}!`;
}
// Arrow function with types
const 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 parameters
function increment(value: number, amount: number = 1): number {
return value + amount;
}
// Rest parameters
function sum(...numbers: number[]): number {
return numbers.reduce((total, n) => total + n, 0);
}
// Function type definition
type 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 signatures
function process(x: number): number;
function process(x: string): string;
// Implementation
function process(x: number | string): number | string {
if (typeof x === "number") {
return x * 2;
} else {
return `${x} processed`;
}
}
const a = process(123); // Returns a number
const 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 point
function 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 type
let user: { name: string; age: number } = {
name: "Alice",
age: 30
};
// Type alias for reuse
type User = {
id: number;
name: string;
email?: string; // Optional property
readonly createdAt: Date; // Can't be modified
};
// Index signature for dynamic properties
type Dictionary = {
[key: string]: string;
};
const colors: Dictionary = {
red: "#ff0000",
green: "#00ff00"
};
// Nested objects
type Customer = {
name: string;
address: {
city: string;
zipCode: string;
}
};

Type Aliases

// Basic type alias
type Point = {
x: number;
y: number;
};
// Union type alias
type Status = 'pending' | 'success' | 'error';
// Function type alias
type Calculator = (x: number, y: number) => number;
// Combining type aliases
type 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 Types
type Animal = {
name: string;
};
type Dog = Animal & {
breed: string;
};
// Generic Types
type List<T> = {
items: T[];
total: number;
};
const numberList: List<number> = {
items: [1, 2, 3],
total: 3
};

Arrays in TypeScript

// Basic array types
let numbers: number[] = [1, 2, 3];
let names: string[] = ["Alice", "Bob"];
// Alternative syntax
let items: Array<number> = [1, 2, 3];
// Mixed types array
let mixed: (string | number)[] = [1, "two", 3];
// Array of objects
type User = { id: number; name: string };
let users: User[] = [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" }
];
// Readonly arrays
const 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 tuple
let person: [string, number] = ["Alice", 25];
// Optional elements
let record: [string, number?] = ["Alice"]; // Second element optional
// Rest elements
let team: [string, ...number[]] = ["Scores", 90, 85, 75];
// Readonly tuple
const 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 enum
enum Direction {
Up, // 0
Down, // 1
Left, // 2
Right // 3
}
// String enum
enum Color {
Red = "RED",
Green = "GREEN",
Blue = "BLUE"
}
// Usage
let 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 options
function setStatus(status: Status) {
// ...
}
// ⚠️ Avoid number enums unless you really need them
// ✅ Prefer string enums or union types for better type safety
type UserRole = "admin" | "user" | "guest"; // Union type alternative

Classes in TypeScript

// Basic class
class 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}`;
}
}
// Inheritance
class 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 implementation
interface Vehicle {
start(): void;
stop(): void;
}
class Car implements Vehicle {
start() { console.log('Starting...'); }
stop() { console.log('Stopping...'); }
}
// Abstract class
abstract 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 Examples
interface Database {
connect(): void;
query(sql: string): any[];
close(): void;
}
// Multiple classes implementing same interface
class 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 inheritance
interface UserService {
findById(id: number): User;
}
interface AdminService extends UserService {
deleteUser(id: number): void;
}
// Class implementing multiple interfaces
interface 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 example
function 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 Example
abstract 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 class
class 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 Example
interface Vehicle {
// Only method signatures, no implementation
start(): void;
stop(): void;
}
interface Electric {
charge(): void;
}
// Class can implement multiple interfaces
class 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");
}
}
// Usage
const 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

  1. Abstract Classes:

    • Can have implemented methods
    • Can have constructor
    • Can have private/protected members
    • Class can extend only one abstract class
  2. 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 Function
function getFirst<Type>(items: Type[]): Type {
return items[0];
}
// Using the function
getFirst([1, 2, 3]); // Returns number
getFirst(["a", "b", "c"]); // Returns string
getFirst([true, false]); // Returns boolean
// Generic Class Example
class Box<Type> {
private content: Type;
constructor(value: Type) {
this.content = value;
}
get(): Type {
return this.content;
}
}
// Using the Box class
const numberBox = new Box(123); // Box<number>
const stringBox = new Box("hello"); // Box<string>
// Generic with Multiple Types
function pair<Type1, Type2>(first: Type1, second: Type2) {
return { first, second };
}
// Using pair function
pair("name", 100); // { first: string, second: number }
pair(true, 42); // { first: boolean, second: number }
// Generic Constraint Example
interface HasName {
name: string;
}
function printName<Type extends HasName>(item: Type) {
console.log(item.name);
}
// Using constrained generic
printName({ name: "John", age: 30 }); // Works
// printName({ age: 30 }); // Error: missing name property