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
- Introduction to TypeScript
- TypeScript Architecture
- Setting Up TypeScript
- Basic Types
- Advanced Types
- Functions
- Interfaces
- Classes
- Generics
- Modules
- Decorators
- Type Guards
- Utility Types
- Best Practices
- 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:
- Type Safety: Catch errors at compile time
- Better IntelliSense: Enhanced autocomplete and suggestions
- Scalability: Easier to maintain large codebases
- Refactoring: Safer code changes
- Documentation: Types serve as inline documentation
- 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 optionalRequired<T>: Makes all properties requiredReadonly<T>: Makes all properties readonlyPick<T, K>: Picks specific propertiesOmit<T, K>: Omits specific propertiesRecord<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 completelyunknown: 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:
- Type System: Static typing, type inference, and type safety
- Interfaces: Contracts for object shapes and APIs
- Classes: Object-oriented programming with access modifiers
- Generics: Reusable, type-safe components
- Advanced Types: Unions, intersections, and utility types
- Modules: Code organization and reusability
- Decorators: Metadata and code enhancement
- Type Guards: Runtime type checking
- 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:
- Practice with real-world projects
- Explore advanced TypeScript features
- Learn framework-specific TypeScript patterns
- Study TypeScript design patterns
- Contribute to open-source TypeScript projects
- 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
- TypeScript Official Documentation
- TypeScript Playground
- DefinitelyTyped
- TypeScript Deep Dive
- TypeScript Handbook