TypeScript Essentials
Complete TypeScript reference covering types, interfaces, generics, and advanced patterns. Essential for building scalable, type-safe JavaScript applications.
Why TypeScript?
TypeScript provides static type checking, enhanced IDE support, and better code maintainability. It compiles to clean, readable JavaScript that runs anywhere.
Benefits: Type safety, IntelliSense, Refactoring tools, Early error detection
Basic Types
Primitive Types
// Basic types
let name: string = 'Alice';
let age: number = 30;
let isActive: boolean = true;
let data: any = { anything: 'goes here' }; // Avoid when possible
let nothing: void = undefined;
let empty: null = null;
let notSet: undefined = undefined;
// Arrays
let numbers: number[] = [1, 2, 3, 4, 5];
let names: Array<string> = ['Alice', 'Bob', 'Charlie'];
let mixed: (string | number)[] = ['Alice', 30, 'Bob', 25];
// Tuples - fixed length arrays
let coordinates: [number, number] = [10, 20];
let userInfo: [string, number, boolean] = ['Alice', 30, true];
// Enums
enum Color {
Red = 'red',
Green = 'green',
Blue = 'blue'
}
let favoriteColor: Color = Color.Blue;
// Union types
let id: string | number = 123;
id = 'ABC123'; // Valid
// Literal types
let status: 'pending' | 'approved' | 'rejected' = 'pending';
let direction: 'north' | 'south' | 'east' | 'west' = 'north';
Interfaces
Interface Definition & Usage
// Basic interface
interface User {
id: number;
name: string;
email: string;
age?: number; // Optional property
readonly createdAt: Date; // Read-only property
}
// Using interface
const user: User = {
id: 1,
name: 'Alice',
email: 'alice@example.com',
createdAt: new Date()
};
// Function interfaces
interface Calculator {
(a: number, b: number): number;
}
const add: Calculator = (a, b) => a + b;
// Method signatures
interface UserService {
getUser(id: number): Promise<User>;
createUser(data: Omit<User, 'id' | 'createdAt'>): Promise<User>;
updateUser(id: number, data: Partial<User>): Promise<User>;
deleteUser(id: number): Promise<void>;
}
// Index signatures
interface StringDictionary {
[key: string]: string;
}
const colors: StringDictionary = {
primary: 'blue',
secondary: 'green'
};
Interface Extension & Merging
// Interface extension
interface Animal {
name: string;
age: number;
}
interface Dog extends Animal {
breed: string;
bark(): void;
}
const myDog: Dog = {
name: 'Buddy',
age: 3,
breed: 'Golden Retriever',
bark() {
console.log('Woof!');
}
};
// Multiple inheritance
interface Flyable {
fly(): void;
}
interface Swimmable {
swim(): void;
}
interface Duck extends Animal, Flyable, Swimmable {
quack(): void;
}
// Declaration merging
interface Config {
apiUrl: string;
}
interface Config {
timeout: number;
}
// Config now has both apiUrl and timeout
Classes
Class Definition & Modifiers
// Basic class with access modifiers
class Person {
public name: string; // Public (default)
private age: number; // Private to class
protected email: string; // Protected (class + subclasses)
readonly id: number; // Read-only after initialization
constructor(name: string, age: number, email: string) {
this.name = name;
this.age = age;
this.email = email;
this.id = Math.random();
}
public greet(): string {
return `Hello, I'm ${this.name}`;
}
private calculateBirthYear(): number {
return new Date().getFullYear() - this.age;
}
protected getContactInfo(): string {
return this.email;
}
// Getter/Setter
get userAge(): number {
return this.age;
}
set userAge(value: number) {
if (value > 0 && value < 150) {
this.age = value;
}
}
// Static methods
static species(): string {
return 'Homo sapiens';
}
}
Inheritance & Abstract Classes
// Abstract base class
abstract class Vehicle {
protected brand: string;
constructor(brand: string) {
this.brand = brand;
}
abstract start(): void; // Must be implemented
getBrand(): string {
return this.brand;
}
}
// Concrete implementation
class Car extends Vehicle {
private model: string;
constructor(brand: string, model: string) {
super(brand); // Call parent constructor
this.model = model;
}
start(): void {
console.log(`Starting ${this.brand} ${this.model}`);
}
getModel(): string {
return this.model;
}
}
// Implementing interfaces
interface Drivable {
drive(): void;
}
class SportsCar extends Car implements Drivable {
drive(): void {
console.log('Driving fast!');
}
}
Generics
Generic Functions & Classes
// Generic function
function identity<T>(arg: T): T {
return arg;
}
const stringResult = identity<string>('hello');
const numberResult = identity<number>(42);
const autoInferred = identity('auto'); // Type inferred
// Generic with constraints
interface Lengthwise {
length: number;
}
function logLength<T extends Lengthwise>(arg: T): T {
console.log(arg.length);
return arg;
}
logLength('hello'); // ✅ string has length
logLength([1, 2, 3]); // ✅ array has length
// logLength(123); // ❌ number doesn't have length
// Generic class
class Container<T> {
private items: T[] = [];
add(item: T): void {
this.items.push(item);
}
get(index: number): T | undefined {
return this.items[index];
}
getAll(): T[] {
return [...this.items];
}
}
const stringContainer = new Container<string>();
const numberContainer = new Container<number>();
Advanced Generic Patterns
// Generic interfaces
interface Repository<T> {
findById(id: string): Promise<T | null>;
save(entity: T): Promise<T>;
delete(id: string): Promise<void>;
}
// Multiple type parameters
interface KeyValuePair<K, V> {
key: K;
value: V;
}
const pair: KeyValuePair<string, number> = {
key: 'age',
value: 30
};
// Generic utility function
function pick<T, K extends keyof T>(obj: T, keys: K[]): Pick<T, K> {
const result = {} as Pick<T, K>;
keys.forEach(key => {
result[key] = obj[key];
});
return result;
}
interface User {
id: number;
name: string;
email: string;
age: number;
}
const user: User = { id: 1, name: 'Alice', email: 'alice@example.com', age: 30 };
const nameAndEmail = pick(user, ['name', 'email']);
// Type: { name: string; email: string; }
Utility Types
Built-in Utility Types
interface User {
id: number;
name: string;
email: string;
age: number;
isActive: boolean;
}
// Partial - all properties optional
type PartialUser = Partial<User>;
const updateData: PartialUser = { name: 'New Name' };
// Required - all properties required
type RequiredUser = Required<User>;
// Pick - select specific properties
type UserPreview = Pick<User, 'id' | 'name'>;
const preview: UserPreview = { id: 1, name: 'Alice' };
// Omit - exclude specific properties
type CreateUser = Omit<User, 'id'>;
const newUser: CreateUser = {
name: 'Bob',
email: 'bob@example.com',
age: 25,
isActive: true
};
// Record - object with specific key-value types
type UserRoles = Record<'admin' | 'user' | 'guest', string[]>;
const roles: UserRoles = {
admin: ['read', 'write', 'delete'],
user: ['read', 'write'],
guest: ['read']
};
// Exclude/Extract - for union types
type Status = 'pending' | 'approved' | 'rejected' | 'cancelled';
type ActiveStatus = Exclude<Status, 'cancelled'>; // 'pending' | 'approved' | 'rejected'
type FinalStatus = Extract<Status, 'approved' | 'rejected'>; // 'approved' | 'rejected'
Type Guards
Type Narrowing & Guards
// typeof type guard
function processValue(value: string | number) {
if (typeof value === 'string') {
return value.toUpperCase(); // TypeScript knows it's string
} else {
return value.toFixed(2); // TypeScript knows it's number
}
}
// instanceof type guard
class Dog {
bark() { console.log('Woof!'); }
}
class Cat {
meow() { console.log('Meow!'); }
}
function makeSound(animal: Dog | Cat) {
if (animal instanceof Dog) {
animal.bark(); // TypeScript knows it's Dog
} else {
animal.meow(); // TypeScript knows it's Cat
}
}
// Custom type guard
interface Fish {
swim(): void;
}
interface Bird {
fly(): void;
}
function isFish(animal: Fish | Bird): animal is Fish {
return 'swim' in animal;
}
function move(animal: Fish | Bird) {
if (isFish(animal)) {
animal.swim(); // TypeScript knows it's Fish
} else {
animal.fly(); // TypeScript knows it's Bird
}
}
// Discriminated unions
interface LoadingState {
status: 'loading';
}
interface SuccessState {
status: 'success';
data: any;
}
interface ErrorState {
status: 'error';
error: string;
}
type AppState = LoadingState | SuccessState | ErrorState;
function handleState(state: AppState) {
switch (state.status) {
case 'loading':
console.log('Loading...');
break;
case 'success':
console.log('Data:', state.data); // data available
break;
case 'error':
console.log('Error:', state.error); // error available
break;
}
}
Advanced Types
Conditional & Mapped Types
// Conditional types
type NonNullable<T> = T extends null | undefined ? never : T;
type StringOrNumber<T> = T extends string ? string : number;
// Mapped types
type Optional<T> = {
[K in keyof T]?: T[K];
};
type Readonly<T> = {
readonly [K in keyof T]: T[K];
};
// Template literal types
type Direction = 'top' | 'bottom' | 'left' | 'right';
type Margin = `margin-${Direction}`; // 'margin-top' | 'margin-bottom' | ...
// Index access types
interface User {
profile: {
name: string;
avatar: string;
};
settings: {
theme: 'light' | 'dark';
};
}
type UserProfile = User['profile']; // { name: string; avatar: string; }
type Theme = User['settings']['theme']; // 'light' | 'dark'
// keyof operator
type UserKeys = keyof User; // 'profile' | 'settings'
// typeof operator
const config = {
apiUrl: 'https://api.example.com',
timeout: 5000,
retries: 3
};
type Config = typeof config;
// { apiUrl: string; timeout: number; retries: number; }
Decorators
Experimental Decorators
// Enable in tsconfig.json: "experimentalDecorators": true
// Class decorator
function sealed(constructor: Function) {
Object.seal(constructor);
Object.seal(constructor.prototype);
}
@sealed
class Person {
constructor(public name: string) {}
}
// Method decorator
function log(target: any, propertyName: string, descriptor: PropertyDescriptor) {
const method = descriptor.value;
descriptor.value = function (...args: any[]) {
console.log(`Calling ${propertyName} with`, args);
return method.apply(this, args);
};
}
class Calculator {
@log
add(a: number, b: number): number {
return a + b;
}
}
// Property decorator
function validate(target: any, propertyName: string) {
let value: any;
const getter = () => value;
const setter = (newVal: any) => {
if (typeof newVal !== 'string') {
throw new Error(`${propertyName} must be a string`);
}
value = newVal;
};
Object.defineProperty(target, propertyName, {
get: getter,
set: setter
});
}
class User {
@validate
name: string;
}
⚠️ Experimental Feature:
Decorators are experimental. Enable with `"experimentalDecorators": true` in tsconfig.json. Consider using the newer Stage 3 decorator proposal when available.
Modules & Namespaces
Module Declaration & Ambient Modules
// Declaration merging with modules
declare module 'express' {
interface Request {
user?: {
id: string;
name: string;
};
}
}
// Global type declarations
declare global {
interface Window {
customProperty: string;
}
}
// Namespace (legacy, prefer modules)
namespace Utilities {
export function formatDate(date: Date): string {
return date.toISOString().split('T')[0];
}
export namespace Validation {
export function isEmail(str: string): boolean {
return /^[^@]+@[^@]+\.[^@]+$/.test(str);
}
}
}
// Usage
const formattedDate = Utilities.formatDate(new Date());
const isValid = Utilities.Validation.isEmail('test@example.com');
// Triple-slash directives
/// <reference path="./types.d.ts" />
/// <reference types="node" />
Configuration
tsconfig.json
{
"compilerOptions": {
"target": "ES2020", // Output ECMAScript version
"module": "commonjs", // Module system
"lib": ["ES2020", "DOM"], // Include type definitions
"outDir": "./dist", // Output directory
"rootDir": "./src", // Input directory
"strict": true, // Enable all strict checks
"esModuleInterop": true, // Enable ES module interop
"skipLibCheck": true, // Skip type checking of declaration files
"forceConsistentCasingInFileNames": true, // Enforce consistent casing
"declaration": true, // Generate .d.ts files
"sourceMap": true, // Generate source maps
"removeComments": true, // Remove comments in output
"noImplicitAny": true, // Error on implicit any
"noImplicitReturns": true, // Error on missing return statements
"noUnusedLocals": true, // Error on unused locals
"noUnusedParameters": true, // Error on unused parameters
"exactOptionalPropertyTypes": true, // Exact optional properties
"noImplicitOverride": true, // Require explicit override
"allowUnreachableCode": false, // Error on unreachable code
"paths": { // Path mapping
"@/*": ["./src/*"],
"@components/*": ["./src/components/*"]
}
},
"include": ["src/**/*"], // Include files
"exclude": ["node_modules", "dist"] // Exclude files
}
Best Practices
TypeScript Best Practices
// ✅ Good: Use specific types
function getUserById(id: string): Promise<User | null> {
// Implementation
}
// ❌ Bad: Using any
function getUserById(id: any): any {
// Implementation
}
// ✅ Good: Use const assertions
const colors = ['red', 'green', 'blue'] as const;
type Color = typeof colors[number]; // 'red' | 'green' | 'blue'
// ✅ Good: Use branded types for IDs
type UserId = string & { __brand: 'UserId' };
type ProductId = string & { __brand: 'ProductId' };
function getUser(id: UserId): User {
// Implementation
}
// ✅ Good: Use readonly for immutable data
interface Config {
readonly apiUrl: string;
readonly features: readonly string[];
}
// ✅ Good: Use unknown instead of any for inputs
function parseJSON(json: string): unknown {
return JSON.parse(json);
}
// ✅ Good: Use assertion functions
function assertIsNumber(value: unknown): asserts value is number {
if (typeof value !== 'number') {
throw new Error('Expected number');
}
}
🎯 TypeScript Mastery Tips
- • Enable strict mode and gradually increase strictness
- • Use type-only imports: `import type { 'User' } from './types'`
- • Prefer interfaces for object shapes, types for unions/intersections
- • Use satisfies operator for type checking without widening
- • Create utility types for common patterns in your codebase
- • Use branded types for better type safety with similar primitive types
- • Leverage template literal types for API route types
- • Use const assertions to create narrow literal types
🚀 TypeScript Excellence
TypeScript transforms JavaScript development by providing type safety, better tooling, and improved code maintainability. Master these patterns to build robust applications.