Full Stack • Java • System Design • Cloud • AI Engineering

TypeScript2026-06-08

TypeScript Basics - Complete Guide

Comprehensive guide to TypeScript fundamentals: static typing, interfaces, classes, and how TypeScript enhances JavaScript development with detailed examples and best practices.

TypeScript Basics

TypeScript is a strongly typed superset of JavaScript developed by Microsoft that compiles to plain JavaScript. It adds optional static typing, classes, interfaces, and other features to JavaScript, making it more robust and maintainable for large-scale applications.

Table of Contents

  1. Introduction to TypeScript
  2. TypeScript Architecture
  3. Setting Up TypeScript
  4. Basic Types
  5. Advanced Types
  6. Functions
  7. Interfaces
  8. Classes
  9. Generics
  10. Modules
  11. Decorators
  12. Type Guards
  13. Utility Types
  14. Best Practices
  15. Real-World Examples

Introduction to TypeScript

What is TypeScript?

TypeScript is JavaScript with syntax for types. It's a strongly typed programming language that builds on JavaScript, giving you better tooling at any scale.

Key Features

✓ Static Type Checking
✓ Enhanced IDE Support
✓ Early Error Detection
✓ Better Code Documentation
✓ Improved Refactoring
✓ Object-Oriented Features
✓ Modern JavaScript Features
✓ Backward Compatibility

Why TypeScript?

Benefits:

  1. Type Safety: Catch errors at compile time
  2. Better IntelliSense: Enhanced autocomplete and suggestions
  3. Scalability: Easier to maintain large codebases
  4. Refactoring: Safer code changes
  5. Documentation: Types serve as inline documentation
  6. Team Collaboration: Clear contracts between code modules

Popular Frameworks Using TypeScript:

  • Angular (built with TypeScript)
  • React (excellent TypeScript support)
  • Next.js (TypeScript-first)
  • NestJS (Node.js framework)
  • Vue 3 (written in TypeScript)

TypeScript Architecture

Compilation Flow

┌─────────────────────┐
│  TypeScript Code    │
│    (.ts files)      │
└──────────┬──────────┘
           │
           ▼
┌─────────────────────┐
│  TypeScript         │
│  Compiler (tsc)     │
│                     │
│  • Type Checking    │
│  • Transpilation    │
│  • Source Maps      │
└──────────┬──────────┘
           │
           ▼
┌─────────────────────┐
│  JavaScript Code    │
│    (.js files)      │
└──────────┬──────────┘
           │
           ▼
┌─────────────────────┐
│  Runtime            │
│  • Browser          │
│  • Node.js          │
│  • Deno             │
└─────────────────────┘

TypeScript Compiler Process

Source Code → Parser → AST → Type Checker → Emitter → JavaScript
                                    ↓
                              Type Errors

Setting Up TypeScript

Installation

# Global installation
npm install -g typescript

# Project installation
npm install --save-dev typescript

# Check version
tsc --version

Initialize TypeScript Project

# Create tsconfig.json
tsc --init

# Create project structure
mkdir src dist

tsconfig.json Configuration

{
  "compilerOptions": {
    "target": "ES2022",                    // JavaScript version
    "module": "commonjs",                  // Module system
    "lib": ["ES2022", "DOM"],             // Type definitions
    "outDir": "./dist",                    // Output directory
    "rootDir": "./src",                    // Source directory
    "strict": true,                        // Enable strict checks
    "esModuleInterop": true,              // CommonJS/ES6 interop
    "skipLibCheck": true,                  // Skip lib checks
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,             // Import JSON
    "declaration": true,                   // Generate .d.ts files
    "sourceMap": true,                     // Generate source maps
    "removeComments": true,                // Remove comments
    "noUnusedLocals": true,               // Report unused locals
    "noUnusedParameters": true,           // Report unused params
    "noImplicitReturns": true,            // Check return statements
    "noFallthroughCasesInSwitch": true    // Check switch cases
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}

Compile and Run

# Compile TypeScript
tsc

# Compile specific file
tsc app.ts

# Watch mode
tsc --watch

# Run with ts-node (development)
npm install -g ts-node
ts-node src/app.ts

Basic Types

Primitive Types

// String
let name: string = "Venu";
let greeting: string = `Hello, ${name}!`;

// Number
let age: number = 35;
let salary: number = 150000;
let pi: number = 3.14159;

// Boolean
let isActive: boolean = true;
let hasPermission: boolean = false;

// BigInt (ES2020+)
let bigNumber: bigint = 9007199254740991n;

// Symbol
let sym: symbol = Symbol("unique");

// Null and Undefined
let nullValue: null = null;
let undefinedValue: undefined = undefined;

Type Inference

TypeScript automatically infers types when not explicitly declared:

// Type inference
let city = "San Antonio";  // inferred as string
let count = 42;            // inferred as number
let active = true;         // inferred as boolean

// Equivalent to:
let city: string = "San Antonio";
let count: number = 42;
let active: boolean = true;

Arrays

// Array of strings
let skills: string[] = ["Java", "Spring", "AWS"];

// Alternative syntax
let languages: Array<string> = ["TypeScript", "JavaScript"];

// Array of numbers
let scores: number[] = [95, 87, 92, 88];

// Mixed array (not recommended)
let mixed: any[] = [1, "two", true];

// Read-only array
let readOnlyArray: ReadonlyArray<number> = [1, 2, 3];
// readOnlyArray.push(4); // Error: Property 'push' does not exist

Tuples

Fixed-length arrays with specific types:

// Basic tuple
let user: [string, number] = ["Venu", 35];

// Accessing tuple elements
console.log(user[0]); // "Venu"
console.log(user[1]); // 35

// Tuple with optional elements
let employee: [string, number, boolean?] = ["John", 101];

// Named tuples (TypeScript 4.0+)
let person: [name: string, age: number] = ["Alice", 30];

// Rest elements in tuples
let data: [string, ...number[]] = ["scores", 95, 87, 92];

Enums

// Numeric enum
enum Role {
  ADMIN,      // 0
  USER,       // 1
  MANAGER,    // 2
  GUEST       // 3
}

let userRole: Role = Role.ADMIN;
console.log(userRole); // 0

// String enum
enum Status {
  PENDING = "PENDING",
  APPROVED = "APPROVED",
  REJECTED = "REJECTED"
}

let orderStatus: Status = Status.PENDING;

// Heterogeneous enum (mixed)
enum Mixed {
  No = 0,
  Yes = "YES"
}

// Const enum (optimized)
const enum Direction {
  Up,
  Down,
  Left,
  Right
}

let move: Direction = Direction.Up;

Any Type

// Any type (disables type checking)
let value: any = "TypeScript";
value = 100;        // OK
value = true;       // OK
value = {};         // OK

// Use sparingly - defeats TypeScript's purpose
function processData(data: any) {
  return data;
}

Unknown Type

Safer alternative to any:

// Unknown type
let value: unknown;

value = "Hello";
value = 42;
value = true;

// Type checking required before use
if (typeof value === "string") {
  console.log(value.toUpperCase()); // OK
}

// Type assertion
let strValue: string = value as string;

Void, Never, and Object

// Void - function returns nothing
function logMessage(message: string): void {
  console.log(message);
}

// Never - function never returns
function throwError(message: string): never {
  throw new Error(message);
}

function infiniteLoop(): never {
  while (true) {
    // infinite loop
  }
}

// Object type
let obj: object = { name: "Venu" };
let user: { name: string; age: number } = {
  name: "John",
  age: 30
};

Advanced Types

Union Types

// Union type - multiple possible types
let id: number | string;

id = 101;        // OK
id = "EMP101";   // OK
// id = true;    // Error

// Function with union parameter
function printId(id: number | string) {
  if (typeof id === "string") {
    console.log(id.toUpperCase());
  } else {
    console.log(id.toFixed(2));
  }
}

// Union with literal types
type Status = "pending" | "approved" | "rejected";
let orderStatus: Status = "pending";

Intersection Types

// Intersection type - combine multiple types
interface Person {
  name: string;
  age: number;
}

interface Employee {
  employeeId: number;
  department: string;
}

type Staff = Person & Employee;

const staff: Staff = {
  name: "John",
  age: 30,
  employeeId: 101,
  department: "IT"
};

Type Aliases

// Type alias
type UserID = number | string;
type Point = { x: number; y: number };

let userId: UserID = 101;
let point: Point = { x: 10, y: 20 };

// Complex type alias
type ApiResponse<T> = {
  data: T;
  status: number;
  message: string;
};

type UserResponse = ApiResponse<{ id: number; name: string }>;

Literal Types

// String literal types
type Direction = "north" | "south" | "east" | "west";
let heading: Direction = "north";

// Numeric literal types
type DiceRoll = 1 | 2 | 3 | 4 | 5 | 6;
let roll: DiceRoll = 4;

// Boolean literal type
type Success = true;
let result: Success = true;

Type Assertions

// Type assertion (angle bracket syntax)
let value: any = "TypeScript";
let length: number = (<string>value).length;

// Type assertion (as syntax - preferred)
let strLength: number = (value as string).length;

// Non-null assertion
function processValue(value: string | null) {
  // Assert value is not null
  console.log(value!.toUpperCase());
}

// Const assertion
let config = {
  host: "localhost",
  port: 3000
} as const;

// config.port = 4000; // Error: Cannot assign to 'port'

Functions

Function Types

// Function declaration
function add(a: number, b: number): number {
  return a + b;
}

// Function expression
const subtract = function(a: number, b: number): number {
  return a - b;
};

// Arrow function
const multiply = (a: number, b: number): number => a * b;

// Function type
type MathOperation = (a: number, b: number) => number;

const divide: MathOperation = (a, b) => a / b;

Optional and Default Parameters

// Optional parameter
function greet(name: string, greeting?: string): string {
  return greeting ? `${greeting}, ${name}!` : `Hello, ${name}!`;
}

console.log(greet("Venu"));              // "Hello, Venu!"
console.log(greet("Venu", "Welcome"));   // "Welcome, Venu!"

// Default parameter
function createUser(name: string, role: string = "user"): void {
  console.log(`${name} - ${role}`);
}

createUser("John");           // "John - user"
createUser("Admin", "admin"); // "Admin - admin"

Rest Parameters

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

console.log(sum(1, 2, 3, 4, 5)); // 15

// Rest with other parameters
function buildMessage(prefix: string, ...parts: string[]): string {
  return prefix + " " + parts.join(" ");
}

Function Overloading

// Function overload signatures
function process(value: string): string;
function process(value: number): number;
function process(value: boolean): boolean;

// Implementation
function process(value: string | number | boolean): string | number | boolean {
  if (typeof value === "string") {
    return value.toUpperCase();
  } else if (typeof value === "number") {
    return value * 2;
  } else {
    return !value;
  }
}

console.log(process("hello"));  // "HELLO"
console.log(process(5));        // 10
console.log(process(true));     // false

Callback Functions

// Callback function type
type Callback = (error: Error | null, result?: any) => void;

function fetchData(url: string, callback: Callback): void {
  // Simulate async operation
  setTimeout(() => {
    if (url) {
      callback(null, { data: "Success" });
    } else {
      callback(new Error("Invalid URL"));
    }
  }, 1000);
}

// Usage
fetchData("https://api.example.com", (error, result) => {
  if (error) {
    console.error(error);
  } else {
    console.log(result);
  }
});

Interfaces

Basic Interface

// Interface definition
interface User {
  id: number;
  name: string;
  email: string;
  age?: number;  // optional property
}

// Implementation
const user: User = {
  id: 1,
  name: "Venu",
  email: "[email protected]"
};

// Function parameter
function displayUser(user: User): void {
  console.log(`${user.name} (${user.email})`);
}

Interface Extension

// Base interface
interface Person {
  name: string;
  age: number;
}

// Extended interface
interface Employee extends Person {
  employeeId: number;
  department: string;
  salary: number;
}

const employee: Employee = {
  name: "John",
  age: 30,
  employeeId: 101,
  department: "IT",
  salary: 75000
};

// Multiple inheritance
interface Manager extends Employee, Person {
  teamSize: number;
}

Readonly Properties

interface Config {
  readonly apiUrl: string;
  readonly timeout: number;
}

const config: Config = {
  apiUrl: "https://api.example.com",
  timeout: 5000
};

// config.apiUrl = "new-url"; // Error: Cannot assign to 'apiUrl'

Index Signatures

// String index signature
interface StringDictionary {
  [key: string]: string;
}

const dict: StringDictionary = {
  name: "Venu",
  city: "San Antonio",
  country: "USA"
};

// Number index signature
interface NumberArray {
  [index: number]: number;
}

const scores: NumberArray = [95, 87, 92, 88];

Function Interfaces

// Function interface
interface MathFunc {
  (a: number, b: number): number;
}

const add: MathFunc = (x, y) => x + y;
const subtract: MathFunc = (x, y) => x - y;

// Interface with methods
interface Calculator {
  add(a: number, b: number): number;
  subtract(a: number, b: number): number;
  multiply(a: number, b: number): number;
}

const calculator: Calculator = {
  add: (a, b) => a + b,
  subtract: (a, b) => a - b,
  multiply: (a, b) => a * b
};

Interface vs Type Alias

// Interface
interface UserInterface {
  id: number;
  name: string;
}

// Type alias
type UserType = {
  id: number;
  name: string;
};

// Key differences:
// 1. Interfaces can be extended
interface Admin extends UserInterface {
  role: string;
}

// 2. Types support unions
type ID = number | string;

// 3. Interfaces support declaration merging
interface Window {
  customProperty: string;
}

interface Window {
  anotherProperty: number;
}

Classes

Basic Class

// Class definition
class User {
  // Properties
  id: number;
  name: string;
  email: string;

  // Constructor
  constructor(id: number, name: string, email: string) {
    this.id = id;
    this.name = name;
    this.email = email;
  }

  // Method
  displayInfo(): void {
    console.log(`${this.name} (${this.email})`);
  }
}

// Create instance
const user = new User(1, "Venu", "[email protected]");
user.displayInfo();

Access Modifiers

class Employee {
  // Public (default)
  public name: string;

  // Private (only within class)
  private salary: number;

  // Protected (class and subclasses)
  protected department: string;

  constructor(name: string, salary: number, department: string) {
    this.name = name;
    this.salary = salary;
    this.department = department;
  }

  // Public method
  public getInfo(): string {
    return `${this.name} - ${this.department}`;
  }

  // Private method
  private calculateBonus(): number {
    return this.salary * 0.1;
  }

  // Protected method
  protected getDepartment(): string {
    return this.department;
  }
}

const emp = new Employee("John", 75000, "IT");
console.log(emp.name);        // OK
// console.log(emp.salary);   // Error: private
// console.log(emp.department); // Error: protected

Shorthand Constructor

// Shorthand property declaration
class Product {
  constructor(
    public id: number,
    public name: string,
    private price: number
  ) {}

  getPrice(): number {
    return this.price;
  }
}

const product = new Product(1, "Laptop", 1200);
console.log(product.name);  // "Laptop"

Getters and Setters

class BankAccount {
  private _balance: number = 0;

  // Getter
  get balance(): number {
    return this._balance;
  }

  // Setter
  set balance(amount: number) {
    if (amount < 0) {
      throw new Error("Balance cannot be negative");
    }
    this._balance = amount;
  }

  deposit(amount: number): void {
    this.balance += amount;
  }
}

const account = new BankAccount();
account.balance = 1000;
console.log(account.balance); // 1000

Inheritance

// Base class
class Animal {
  constructor(public name: string) {}

  move(distance: number = 0): void {
    console.log(`${this.name} moved ${distance}m`);
  }
}

// Derived class
class Dog extends Animal {
  constructor(name: string, public breed: string) {
    super(name); // Call parent constructor
  }

  bark(): void {
    console.log("Woof! Woof!");
  }

  // Override parent method
  move(distance: number = 5): void {
    console.log("Running...");
    super.move(distance);
  }
}

const dog = new Dog("Buddy", "Golden Retriever");
dog.bark();      // "Woof! Woof!"
dog.move(10);    // "Running..." "Buddy moved 10m"

Abstract Classes

// Abstract class
abstract class Shape {
  constructor(public color: string) {}

  // Abstract method (must be implemented)
  abstract calculateArea(): number;

  // Concrete method
  displayColor(): void {
    console.log(`Color: ${this.color}`);
  }
}

// Concrete class
class Circle extends Shape {
  constructor(color: string, public radius: number) {
    super(color);
  }

  calculateArea(): number {
    return Math.PI * this.radius ** 2;
  }
}

class Rectangle extends Shape {
  constructor(
    color: string,
    public width: number,
    public height: number
  ) {
    super(color);
  }

  calculateArea(): number {
    return this.width * this.height;
  }
}

const circle = new Circle("red", 5);
console.log(circle.calculateArea()); // 78.54

Static Members

class MathUtils {
  static PI: number = 3.14159;

  static calculateCircleArea(radius: number): number {
    return this.PI * radius ** 2;
  }

  static max(...numbers: number[]): number {
    return Math.max(...numbers);
  }
}

// Access static members
console.log(MathUtils.PI);                    // 3.14159
console.log(MathUtils.calculateCircleArea(5)); // 78.54
console.log(MathUtils.max(10, 20, 30));       // 30

Implementing Interfaces

interface Printable {
  print(): void;
}

interface Saveable {
  save(): void;
}

class Document implements Printable, Saveable {
  constructor(public content: string) {}

  print(): void {
    console.log(`Printing: ${this.content}`);
  }

  save(): void {
    console.log(`Saving: ${this.content}`);
  }
}

const doc = new Document("Hello World");
doc.print();
doc.save();

Generics

Generic Functions

// Generic function
function identity<T>(value: T): T {
  return value;
}

// Usage
let str = identity<string>("TypeScript");
let num = identity<number>(42);
let bool = identity<boolean>(true);

// Type inference
let inferred = identity("Hello"); // T inferred as string

// Generic with constraints
function getLength<T extends { length: number }>(item: T): number {
  return item.length;
}

console.log(getLength("Hello"));      // 5
console.log(getLength([1, 2, 3]));    // 3
// console.log(getLength(123));       // Error: no length property

Generic Interfaces

// Generic interface
interface Repository<T> {
  getById(id: number): T;
  getAll(): T[];
  save(item: T): void;
  delete(id: number): void;
}

// Implementation
class UserRepository implements Repository<User> {
  private users: User[] = [];

  getById(id: number): User {
    return this.users.find(u => u.id === id)!;
  }

  getAll(): User[] {
    return this.users;
  }

  save(user: User): void {
    this.users.push(user);
  }

  delete(id: number): void {
    this.users = this.users.filter(u => u.id !== id);
  }
}

Generic Classes

// Generic class
class DataStore<T> {
  private data: T[] = [];

  add(item: T): void {
    this.data.push(item);
  }

  remove(item: T): void {
    const index = this.data.indexOf(item);
    if (index > -1) {
      this.data.splice(index, 1);
    }
  }

  getAll(): T[] {
    return this.data;
  }

  find(predicate: (item: T) => boolean): T | undefined {
    return this.data.find(predicate);
  }
}

// Usage
const numberStore = new DataStore<number>();
numberStore.add(1);
numberStore.add(2);
numberStore.add(3);

const stringStore = new DataStore<string>();
stringStore.add("Hello");
stringStore.add("World");

Generic Constraints

// Constraint with interface
interface HasId {
  id: number;
}

function findById<T extends HasId>(items: T[], id: number): T | undefined {
  return items.find(item => item.id === id);
}

// Multiple constraints
interface Nameable {
  name: string;
}

interface Ageable {
  age: number;
}

function displayInfo<T extends Nameable & Ageable>(person: T): void {
  console.log(`${person.name} is ${person.age} years old`);
}

Generic Type Parameters

// Multiple type parameters
function merge<T, U>(obj1: T, obj2: U): T & U {
  return { ...obj1, ...obj2 };
}

const merged = merge(
  { name: "John" },
  { age: 30 }
);

console.log(merged); // { name: "John", age: 30 }

// Default type parameters
interface ApiResponse<T = any> {
  data: T;
  status: number;
}

const response: ApiResponse = {
  data: "Hello",
  status: 200
};

Modules

Export and Import

// user.ts - Named exports
export interface User {
  id: number;
  name: string;
}

export class UserService {
  getUser(id: number): User {
    return { id, name: "John" };
  }
}

export const API_URL = "https://api.example.com";

// app.ts - Named imports
import { User, UserService, API_URL } from "./user";

const service = new UserService();
const user: User = service.getUser(1);

Default Exports

// calculator.ts - Default export
export default class Calculator {
  add(a: number, b: number): number {
    return a + b;
  }
}

// app.ts - Default import
import Calculator from "./calculator";

const calc = new Calculator();
console.log(calc.add(5, 3));

Re-exporting

// models/index.ts - Barrel export
export { User, UserService } from "./user";
export { Product, ProductService } from "./product";
export { Order, OrderService } from "./order";

// app.ts - Import from barrel
import { User, Product, Order } from "./models";

Namespace

// Namespace
namespace Validation {
  export interface StringValidator {
    isValid(s: string): boolean;
  }

  export class EmailValidator implements StringValidator {
    isValid(email: string): boolean {
      return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
    }
  }

  export class UrlValidator implements StringValidator {
    isValid(url: string): boolean {
      return /^https?:\/\/.+/.test(url);
    }
  }
}

// Usage
const emailValidator = new Validation.EmailValidator();
console.log(emailValidator.isValid("[email protected]")); // true

Decorators

Class Decorators

// Enable decorators in tsconfig.json:
// "experimentalDecorators": true

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

@sealed
class BugReport {
  type = "report";
  title: string;

  constructor(title: string) {
    this.title = title;
  }
}

Method Decorators

// Method decorator
function log(
  target: any,
  propertyKey: string,
  descriptor: PropertyDescriptor
) {
  const originalMethod = descriptor.value;

  descriptor.value = function(...args: any[]) {
    console.log(`Calling ${propertyKey} with args:`, args);
    const result = originalMethod.apply(this, args);
    console.log(`Result:`, result);
    return result;
  };

  return descriptor;
}

class Calculator {
  @log
  add(a: number, b: number): number {
    return a + b;
  }
}

const calc = new Calculator();
calc.add(5, 3);
// Logs: Calling add with args: [5, 3]
// Logs: Result: 8

Property Decorators

// Property decorator
function readonly(target: any, propertyKey: string) {
  Object.defineProperty(target, propertyKey, {
    writable: false
  });
}

class Person {
  @readonly
  name: string = "John";
}

const person = new Person();
// person.name = "Jane"; // Error in strict mode

Type Guards

typeof Type Guards

function processValue(value: string | number) {
  if (typeof value === "string") {
    // value is string
    return value.toUpperCase();
  } else {
    // value is number
    return value.toFixed(2);
  }
}

instanceof Type Guards

class Dog {
  bark() {
    console.log("Woof!");
  }
}

class Cat {
  meow() {
    console.log("Meow!");
  }
}

function makeSound(animal: Dog | Cat) {
  if (animal instanceof Dog) {
    animal.bark();
  } else {
    animal.meow();
  }
}

Custom Type Guards

// User-defined type guard
interface Fish {
  swim(): void;
}

interface Bird {
  fly(): void;
}

function isFish(pet: Fish | Bird): pet is Fish {
  return (pet as Fish).swim !== undefined;
}

function move(pet: Fish | Bird) {
  if (isFish(pet)) {
    pet.swim();
  } else {
    pet.fly();
  }
}

in Operator

interface Admin {
  name: string;
  privileges: string[];
}

interface Employee {
  name: string;
  startDate: Date;
}

type UnknownEmployee = Admin | Employee;

function printEmployeeInfo(emp: UnknownEmployee) {
  console.log(`Name: ${emp.name}`);
  
  if ("privileges" in emp) {
    console.log(`Privileges: ${emp.privileges}`);
  }
  
  if ("startDate" in emp) {
    console.log(`Start Date: ${emp.startDate}`);
  }
}

Utility Types

Partial

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

// All properties optional
type PartialUser = Partial<User>;

function updateUser(id: number, updates: Partial<User>) {
  // Update only provided fields
}

updateUser(1, { name: "John" });
updateUser(2, { email: "[email protected]", age: 30 });

Required

interface Config {
  host?: string;
  port?: number;
  timeout?: number;
}

// All properties required
type RequiredConfig = Required<Config>;

const config: RequiredConfig = {
  host: "localhost",
  port: 3000,
  timeout: 5000
};

Readonly

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

// All properties readonly
type ReadonlyUser = Readonly<User>;

const user: ReadonlyUser = {
  id: 1,
  name: "John"
};

// user.name = "Jane"; // Error: Cannot assign to 'name'

Pick<T, K>

interface User {
  id: number;
  name: string;
  email: string;
  age: number;
  address: string;
}

// Pick specific properties
type UserPreview = Pick<User, "id" | "name" | "email">;

const preview: UserPreview = {
  id: 1,
  name: "John",
  email: "[email protected]"
};

Omit<T, K>

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

// Omit specific properties
type UserWithoutPassword = Omit<User, "password">;

const user: UserWithoutPassword = {
  id: 1,
  name: "John",
  email: "[email protected]"
};

Record<K, T>

// Create object type with specific keys and value type
type Role = "admin" | "user" | "guest";

type Permissions = Record<Role, string[]>;

const permissions: Permissions = {
  admin: ["read", "write", "delete"],
  user: ["read", "write"],
  guest: ["read"]
};

ReturnType

function createUser() {
  return {
    id: 1,
    name: "John",
    email: "[email protected]"
  };
}

// Extract return type
type User = ReturnType<typeof createUser>;

const user: User = {
  id: 2,
  name: "Jane",
  email: "[email protected]"
};

Best Practices

1. Enable Strict Mode

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

2. Avoid Any Type

// Bad
function processData(data: any) {
  return data;
}

// Good
function processData<T>(data: T): T {
  return data;
}

// Or use unknown
function processData(data: unknown) {
  if (typeof data === "string") {
    return data.toUpperCase();
  }
  return data;
}

3. Use Interfaces for Object Shapes

// Good
interface User {
  id: number;
  name: string;
  email: string;
}

// Use type for unions, intersections
type ID = number | string;
type Result = Success | Error;

4. Prefer Const Assertions

// Mutable
const config = {
  host: "localhost",
  port: 3000
};

// Immutable
const config = {
  host: "localhost",
  port: 3000
} as const;

5. Use Discriminated Unions

interface Success {
  type: "success";
  data: any;
}

interface Error {
  type: "error";
  message: string;
}

type Result = Success | Error;

function handleResult(result: Result) {
  if (result.type === "success") {
    console.log(result.data);
  } else {
    console.error(result.message);
  }
}

6. Leverage Type Inference

// Let TypeScript infer when obvious
const name = "John";  // string
const age = 30;       // number

// Explicit when needed
const users: User[] = [];

7. Use Readonly for Immutability

interface Config {
  readonly apiUrl: string;
  readonly timeout: number;
}

const config: Readonly<Config> = {
  apiUrl: "https://api.example.com",
  timeout: 5000
};

Real-World Examples

Example 1: REST API Client

// API types
interface ApiResponse<T> {
  data: T;
  status: number;
  message: string;
}

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

interface CreateUserDto {
  name: string;
  email: string;
}

// API client
class ApiClient {
  constructor(private baseUrl: string) {}

  async get<T>(endpoint: string): Promise<ApiResponse<T>> {
    const response = await fetch(`${this.baseUrl}${endpoint}`);
    return response.json();
  }

  async post<T, D>(endpoint: string, data: D): Promise<ApiResponse<T>> {
    const response = await fetch(`${this.baseUrl}${endpoint}`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(data)
    });
    return response.json();
  }

  async put<T, D>(endpoint: string, data: D): Promise<ApiResponse<T>> {
    const response = await fetch(`${this.baseUrl}${endpoint}`, {
      method: "PUT",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(data)
    });
    return response.json();
  }

  async delete<T>(endpoint: string): Promise<ApiResponse<T>> {
    const response = await fetch(`${this.baseUrl}${endpoint}`, {
      method: "DELETE"
    });
    return response.json();
  }
}

// User service
class UserService {
  constructor(private api: ApiClient) {}

  async getUsers(): Promise<User[]> {
    const response = await this.api.get<User[]>("/users");
    return response.data;
  }

  async getUserById(id: number): Promise<User> {
    const response = await this.api.get<User>(`/users/${id}`);
    return response.data;
  }

  async createUser(dto: CreateUserDto): Promise<User> {
    const response = await this.api.post<User, CreateUserDto>("/users", dto);
    return response.data;
  }

  async updateUser(id: number, dto: Partial<User>): Promise<User> {
    const response = await this.api.put<User, Partial<User>>(
      `/users/${id}`,
      dto
    );
    return response.data;
  }

  async deleteUser(id: number): Promise<void> {
    await this.api.delete(`/users/${id}`);
  }
}

// Usage
const api = new ApiClient("https://api.example.com");
const userService = new UserService(api);

async function main() {
  const users = await userService.getUsers();
  console.log(users);

  const newUser = await userService.createUser({
    name: "John Doe",
    email: "[email protected]"
  });
  console.log(newUser);
}

Example 2: State Management

// State types
interface AppState {
  user: User | null;
  isLoading: boolean;
  error: string | null;
}

// Action types
type Action =
  | { type: "SET_USER"; payload: User }
  | { type: "SET_LOADING"; payload: boolean }
  | { type: "SET_ERROR"; payload: string }
  | { type: "CLEAR_ERROR" }
  | { type: "LOGOUT" };

// Reducer
function reducer(state: AppState, action: Action): AppState {
  switch (action.type) {
    case "SET_USER":
      return { ...state, user: action.payload, isLoading: false };
    
    case "SET_LOADING":
      return { ...state, isLoading: action.payload };
    
    case "SET_ERROR":
      return { ...state, error: action.payload, isLoading: false };
    
    case "CLEAR_ERROR":
      return { ...state, error: null };
    
    case "LOGOUT":
      return { user: null, isLoading: false, error: null };
    
    default:
      return state;
  }
}

// Store
class Store {
  private state: AppState;
  private listeners: Array<(state: AppState) => void> = [];

  constructor(initialState: AppState) {
    this.state = initialState;
  }

  getState(): AppState {
    return this.state;
  }

  dispatch(action: Action): void {
    this.state = reducer(this.state, action);
    this.listeners.forEach(listener => listener(this.state));
  }

  subscribe(listener: (state: AppState) => void): () => void {
    this.listeners.push(listener);
    return () => {
      this.listeners = this.listeners.filter(l => l !== listener);
    };
  }
}

// Usage
const store = new Store({
  user: null,
  isLoading: false,
  error: null
});

store.subscribe(state => {
  console.log("State updated:", state);
});

store.dispatch({ type: "SET_LOADING", payload: true });
store.dispatch({
  type: "SET_USER",
  payload: { id: 1, name: "John", email: "[email protected]" }
});

Example 3: Form Validation

// Validation types
type ValidationRule<T> = (value: T) => string | null;

interface FormField<T> {
  value: T;
  error: string | null;
  touched: boolean;
}

interface FormState {
  [key: string]: FormField<any>;
}

// Validators
const required: ValidationRule<string> = (value) => {
  return value.trim() ? null : "This field is required";
};

const email: ValidationRule<string> = (value) => {
  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  return emailRegex.test(value) ? null : "Invalid email address";
};

const minLength = (min: number): ValidationRule<string> => {
  return (value) => {
    return value.length >= min
      ? null
      : `Minimum length is ${min} characters`;
  };
};

const maxLength = (max: number): ValidationRule<string> => {
  return (value) => {
    return value.length <= max
      ? null
      : `Maximum length is ${max} characters`;
  };
};

// Form class
class Form {
  private state: FormState = {};
  private validators: { [key: string]: ValidationRule<any>[] } = {};

  addField<T>(name: string, initialValue: T, validators: ValidationRule<T>[] = []) {
    this.state[name] = {
      value: initialValue,
      error: null,
      touched: false
    };
    this.validators[name] = validators;
  }

  setValue(name: string, value: any): void {
    if (this.state[name]) {
      this.state[name].value = value;
      this.state[name].touched = true;
      this.validate(name);
    }
  }

  validate(name: string): boolean {
    const field = this.state[name];
    const validators = this.validators[name] || [];

    for (const validator of validators) {
      const error = validator(field.value);
      if (error) {
        field.error = error;
        return false;
      }
    }

    field.error = null;
    return true;
  }

  validateAll(): boolean {
    let isValid = true;
    for (const name in this.state) {
      if (!this.validate(name)) {
        isValid = false;
      }
    }
    return isValid;
  }

  getValues(): { [key: string]: any } {
    const values: { [key: string]: any } = {};
    for (const name in this.state) {
      values[name] = this.state[name].value;
    }
    return values;
  }

  getErrors(): { [key: string]: string | null } {
    const errors: { [key: string]: string | null } = {};
    for (const name in this.state) {
      errors[name] = this.state[name].error;
    }
    return errors;
  }
}

// Usage
const form = new Form();

form.addField("name", "", [required, minLength(3), maxLength(50)]);
form.addField("email", "", [required, email]);
form.addField("age", "", [required]);

form.setValue("name", "Jo");
console.log(form.getErrors()); // { name: "Minimum length is 3 characters", ... }

form.setValue("name", "John Doe");
form.setValue("email", "[email protected]");
form.setValue("age", "30");

if (form.validateAll()) {
  console.log("Form is valid:", form.getValues());
} else {
  console.log("Form has errors:", form.getErrors());
}

Interview Questions

1. What is TypeScript?

TypeScript is a strongly typed superset of JavaScript that compiles to plain JavaScript. It adds optional static typing, classes, interfaces, and other features to JavaScript.

2. What are the benefits of TypeScript?

  • Type safety and early error detection
  • Better IDE support and IntelliSense
  • Improved code maintainability
  • Enhanced refactoring capabilities
  • Better documentation through types
  • Support for modern JavaScript features

3. Difference between Interface and Type?

  • Primarily for object shapes
  • Can be extended
  • Supports declaration merging
  • Better for public APIs

Type:

  • Supports unions and intersections
  • More flexible
  • Cannot be extended (but can be intersected)
  • Better for complex type compositions

4. What are Generics?

Generics allow you to create reusable, type-safe components that work with multiple types while maintaining type information.

function identity<T>(value: T): T {
  return value;
}

5. What is Type Inference?

Type inference is TypeScript's ability to automatically determine the type of a variable based on its value without explicit type annotation.

6. What is the Unknown type?

unknown is a type-safe alternative to any. It requires type checking before performing operations, making it safer than any.

7. Explain Access Modifiers

  • public: Accessible everywhere (default)
  • private: Only within the class
  • protected: Within class and subclasses

8. What are Utility Types?

Built-in TypeScript types that help transform existing types:

  • Partial<T>: Makes all properties optional
  • Required<T>: Makes all properties required
  • Readonly<T>: Makes all properties readonly
  • Pick<T, K>: Picks specific properties
  • Omit<T, K>: Omits specific properties
  • Record<K, T>: Creates object type with specific keys

9. What is Type Guard?

A type guard is a runtime check that narrows down the type within a conditional block.

function isString(value: any): value is string {
  return typeof value === "string";
}

10. Difference between any and unknown?

  • any: Disables type checking completely
  • unknown: Requires type checking before use (safer)

Cheat Sheet

Basic Types
-----------
string, number, boolean
null, undefined
bigint, symbol

Collections
-----------
Array<T>, T[]
Tuple: [type1, type2]
Enum

Advanced Types
--------------
Union: T | U
Intersection: T & U
Literal Types
Type Aliases

OOP
---
Class
Interface
Abstract Class
Inheritance
Access Modifiers

Functions
---------
Optional Parameters
Default Parameters
Rest Parameters
Function Overloading
Arrow Functions

Generics
--------
Generic Functions
Generic Classes
Generic Interfaces
Generic Constraints

Utility Types
-------------
Partial<T>
Required<T>
Readonly<T>
Pick<T, K>
Omit<T, K>
Record<K, T>
ReturnType<T>

Decorators
----------
Class Decorators
Method Decorators
Property Decorators

Type Guards
-----------
typeof
instanceof
Custom Type Guards
in operator

Modules
-------
export / import
Default exports
Namespace

Compiler
--------
tsc
tsconfig.json
--watch
--strict

Summary

TypeScript brings type safety and maintainability to JavaScript applications, making it the preferred choice for large-scale projects.

Key Concepts Covered:

  1. Type System: Static typing, type inference, and type safety
  2. Interfaces: Contracts for object shapes and APIs
  3. Classes: Object-oriented programming with access modifiers
  4. Generics: Reusable, type-safe components
  5. Advanced Types: Unions, intersections, and utility types
  6. Modules: Code organization and reusability
  7. Decorators: Metadata and code enhancement
  8. Type Guards: Runtime type checking
  9. Best Practices: Strict mode, avoiding any, leveraging inference

Mastering TypeScript Prepares You For:

  • Frontend Development: React, Angular, Vue
  • Backend Development: Node.js, NestJS, Express
  • Full-Stack Development: Next.js, MERN/MEAN stack
  • Enterprise Applications: Large-scale, maintainable codebases
  • Technical Interviews: TypeScript-specific questions
  • Modern JavaScript: ES6+ features with type safety

Next Steps:

  1. Practice with real-world projects
  2. Explore advanced TypeScript features
  3. Learn framework-specific TypeScript patterns
  4. Study TypeScript design patterns
  5. Contribute to open-source TypeScript projects
  6. Master TypeScript configuration and tooling

TypeScript is not just a language—it's a development philosophy that emphasizes type safety, code quality, and developer productivity. By mastering TypeScript fundamentals, you're building a strong foundation for modern web development.


Additional Resources