Prerequisites
- Basic TypeScript types knowledge ๐
- Understanding of objects โก
- Function type basics ๐ป
What you'll learn
- Understand interface fundamentals ๐ฏ
- Create and implement interfaces effectively ๐๏ธ
- Use optional and readonly properties ๐ก๏ธ
- Master interface extension and composition โจ
๐ฏ Introduction
Welcome to the world of TypeScript interfaces! ๐ In this guide, weโll explore one of TypeScriptโs most powerful features for creating contracts that define the shape of objects, ensuring type safety and code clarity.
Youโll discover how interfaces are like blueprints ๐ - they describe what an object should look like without implementing the details. Whether youโre defining API responses ๐, component props ๐จ, or complex data structures ๐๏ธ, understanding interfaces is essential for writing maintainable, type-safe TypeScript code.
By the end of this tutorial, youโll be confidently designing interfaces that make your code more predictable and easier to work with! Letโs dive in! ๐โโ๏ธ
๐ Understanding Interfaces
๐ค What are Interfaces?
Interfaces are like contracts or agreements ๐. Think of them as a promise that says โany object of this type will have these specific properties and methods.โ
In TypeScript terms, interfaces:
- โจ Define the structure of objects
- ๐ Create reusable type definitions
- ๐ก๏ธ Enforce type checking at compile time
- ๐ง Support optional and readonly properties
๐ก Why Use Interfaces?
Hereโs why developers love interfaces:
- Type Safety ๐: Catch errors before runtime
- Code Documentation ๐: Interfaces describe your data
- IntelliSense Support ๐ป: Better autocomplete in IDEs
- Flexibility ๐คธโโ๏ธ: Extend and compose interfaces easily
Real-world example: Imagine building an e-commerce site ๐. An interface for Product
ensures every product has required fields like name
, price
, and id
, preventing bugs from missing data.
๐ง Basic Syntax and Usage
๐ Simple Interface Example
Letโs start with a friendly example:
// ๐ค Define a User interface
interface User {
id: number;
name: string;
email: string;
age: number;
isActive: boolean;
}
// โ
Create objects that match the interface
const user1: User = {
id: 1,
name: 'Alice Johnson',
email: '[email protected]',
age: 28,
isActive: true
};
// โ This would error - missing required properties
// const user2: User = {
// id: 2,
// name: 'Bob Smith'
// // Missing email, age, and isActive!
// };
// ๐ฏ Function using the interface
function greetUser(user: User): string {
return `Hello, ${user.name}! ๐ Your email is ${user.email}`;
}
console.log(greetUser(user1));
// ๐ก TypeScript ensures type safety
function updateUserAge(user: User, newAge: number): User {
return {
...user,
age: newAge
};
}
const updatedUser = updateUserAge(user1, 29);
console.log(`${updatedUser.name} is now ${updatedUser.age} years old! ๐`);
๐ก Explanation: The User
interface defines exactly what properties a user object must have, ensuring consistency across your application!
๐ฏ Optional and Readonly Properties
Not all properties are required or should be modifiable:
// ๐ Address interface with optional and readonly properties
interface Address {
readonly id: string; // ๐ Can't be changed after creation
street: string;
city: string;
state: string;
zipCode: string;
country?: string; // ? Makes it optional
apartment?: string; // ? Optional
specialInstructions?: string; // ? Optional
}
// โ
Create address with only required fields
const address1: Address = {
id: 'ADDR_001',
street: '123 Main St',
city: 'Springfield',
state: 'IL',
zipCode: '62701'
};
// โ
Create address with optional fields
const address2: Address = {
id: 'ADDR_002',
street: '456 Oak Ave',
city: 'Portland',
state: 'OR',
zipCode: '97201',
country: 'USA', // โ
Optional field included
apartment: 'Apt 3B' // โ
Optional field included
};
// โ This would error - can't modify readonly property
// address1.id = 'NEW_ID'; // Error: Cannot assign to 'id' because it is a read-only property
// โ
Other properties can be modified
address1.street = '789 Elm St';
console.log(`๐ Updated address: ${address1.street}`);
// ๐ฏ Function that uses optional properties
function formatAddress(address: Address): string {
let formatted = `${address.street}`;
if (address.apartment) {
formatted += `, ${address.apartment}`;
}
formatted += `\n${address.city}, ${address.state} ${address.zipCode}`;
if (address.country) {
formatted += `\n${address.country}`;
}
return formatted;
}
console.log('๐ฌ Address 1:\n' + formatAddress(address1));
console.log('\n๐ฌ Address 2:\n' + formatAddress(address2));
๐ก Practical Examples
๐ Example 1: E-commerce Product System
Letโs build an e-commerce product system with interfaces:
// ๐ฐ Price information interface
interface Price {
amount: number;
currency: 'USD' | 'EUR' | 'GBP';
discount?: number; // Optional discount percentage
readonly originalAmount: number; // Original price before discount
}
// ๐ฆ Dimensions interface
interface Dimensions {
length: number;
width: number;
height: number;
unit: 'cm' | 'inch';
}
// ๐ท๏ธ Category interface
interface Category {
id: string;
name: string;
parentCategory?: Category; // Self-referential - categories can have parents
}
// ๐๏ธ Main Product interface
interface Product {
readonly id: string;
name: string;
description: string;
price: Price;
inStock: boolean;
stockQuantity: number;
category: Category;
tags: string[];
dimensions?: Dimensions; // Optional - not all products have dimensions
weight?: number; // Optional weight in kg
images: string[];
readonly createdAt: Date;
updatedAt: Date;
}
// ๐ Review interface
interface Review {
id: string;
productId: string;
userId: string;
rating: 1 | 2 | 3 | 4 | 5; // Literal type union
comment: string;
helpful: number;
verified: boolean;
date: Date;
}
// ๐ Shopping cart item interface
interface CartItem {
product: Product;
quantity: number;
addedAt: Date;
}
// ๐๏ธ Shopping cart interface
interface ShoppingCart {
id: string;
userId: string;
items: CartItem[];
readonly createdAt: Date;
lastModified: Date;
}
// ๐ช Product service implementation
class ProductService {
private products: Map<string, Product> = new Map();
// โ Add a new product
addProduct(productData: Omit<Product, 'id' | 'createdAt' | 'updatedAt'>): Product {
const product: Product = {
...productData,
id: `PROD_${Date.now()}`,
createdAt: new Date(),
updatedAt: new Date()
};
this.products.set(product.id, product);
console.log(`โ
Added product: ${product.name} (${product.id})`);
return product;
}
// ๐ฐ Calculate final price
calculateFinalPrice(price: Price): number {
if (price.discount && price.discount > 0) {
const discountAmount = price.originalAmount * (price.discount / 100);
return price.originalAmount - discountAmount;
}
return price.amount;
}
// ๐ Search products by category
findByCategory(categoryId: string): Product[] {
return Array.from(this.products.values()).filter(
product => product.category.id === categoryId
);
}
// ๐ Get product with reviews
getProductWithReviews(productId: string, reviews: Review[]): ProductWithReviews {
const product = this.products.get(productId);
if (!product) {
throw new Error(`Product ${productId} not found!`);
}
const productReviews = reviews.filter(r => r.productId === productId);
const avgRating = productReviews.length > 0
? productReviews.reduce((sum, r) => sum + r.rating, 0) / productReviews.length
: 0;
return {
...product,
reviews: productReviews,
averageRating: avgRating,
reviewCount: productReviews.length
};
}
}
// ๐ Extended interface - Product with reviews
interface ProductWithReviews extends Product {
reviews: Review[];
averageRating: number;
reviewCount: number;
}
// ๐ Cart service implementation
class CartService {
private carts: Map<string, ShoppingCart> = new Map();
// ๐ Create new cart
createCart(userId: string): ShoppingCart {
const cart: ShoppingCart = {
id: `CART_${Date.now()}`,
userId,
items: [],
createdAt: new Date(),
lastModified: new Date()
};
this.carts.set(cart.id, cart);
console.log(`๐ Created cart for user ${userId}`);
return cart;
}
// โ Add item to cart
addToCart(cartId: string, product: Product, quantity: number): void {
const cart = this.carts.get(cartId);
if (!cart) {
throw new Error(`Cart ${cartId} not found!`);
}
const existingItem = cart.items.find(item => item.product.id === product.id);
if (existingItem) {
existingItem.quantity += quantity;
console.log(`๐ฆ Updated quantity for ${product.name}: ${existingItem.quantity}`);
} else {
const cartItem: CartItem = {
product,
quantity,
addedAt: new Date()
};
cart.items.push(cartItem);
console.log(`โ Added ${product.name} to cart`);
}
cart.lastModified = new Date();
}
// ๐ฐ Calculate cart total
calculateCartTotal(cart: ShoppingCart): { subtotal: number; items: number } {
let subtotal = 0;
let items = 0;
cart.items.forEach(item => {
const price = item.product.price;
const finalPrice = price.discount
? price.originalAmount * (1 - price.discount / 100)
: price.amount;
subtotal += finalPrice * item.quantity;
items += item.quantity;
});
return { subtotal, items };
}
}
// ๐ฎ Demo the e-commerce system
const productService = new ProductService();
const cartService = new CartService();
// Create categories
const electronics: Category = {
id: 'CAT_ELECTRONICS',
name: 'Electronics'
};
const computers: Category = {
id: 'CAT_COMPUTERS',
name: 'Computers',
parentCategory: electronics
};
// Create a product
const laptop = productService.addProduct({
name: 'TypeScript Pro Laptop',
description: 'The perfect laptop for TypeScript development',
price: {
amount: 1299.99,
currency: 'USD',
originalAmount: 1299.99,
discount: 10 // 10% off!
},
inStock: true,
stockQuantity: 50,
category: computers,
tags: ['laptop', 'typescript', 'development', 'portable'],
dimensions: {
length: 35,
width: 25,
height: 2,
unit: 'cm'
},
weight: 1.5,
images: ['laptop-front.jpg', 'laptop-side.jpg']
});
// Create some reviews
const reviews: Review[] = [
{
id: 'REV_001',
productId: laptop.id,
userId: 'USER_123',
rating: 5,
comment: 'Amazing laptop for TypeScript development! ๐',
helpful: 42,
verified: true,
date: new Date()
},
{
id: 'REV_002',
productId: laptop.id,
userId: 'USER_456',
rating: 4,
comment: 'Great performance, slightly heavy',
helpful: 15,
verified: true,
date: new Date()
}
];
// Get product with reviews
const productWithReviews = productService.getProductWithReviews(laptop.id, reviews);
console.log(`\nโญ ${productWithReviews.name} - Rating: ${productWithReviews.averageRating.toFixed(1)}/5 (${productWithReviews.reviewCount} reviews)`);
// Create cart and add items
const cart = cartService.createCart('USER_789');
cartService.addToCart(cart.id, laptop, 2);
// Calculate total
const cartTotal = cartService.calculateCartTotal(cart);
console.log(`\n๐ฐ Cart Total: $${cartTotal.subtotal.toFixed(2)} (${cartTotal.items} items)`);
๐ฎ Example 2: Game Development Interfaces
Letโs create a game system with complex interfaces:
// ๐ฎ Vector interface for positions and directions
interface Vector2D {
x: number;
y: number;
}
interface Vector3D extends Vector2D {
z: number;
}
// ๐จ Color interface
interface Color {
r: number; // 0-255
g: number; // 0-255
b: number; // 0-255
a?: number; // Optional alpha channel
}
// ๐โโ๏ธ Animation frame interface
interface AnimationFrame {
duration: number; // milliseconds
sprite: string; // sprite filename
offset?: Vector2D; // Optional position offset
}
// ๐ฌ Animation interface
interface Animation {
name: string;
frames: AnimationFrame[];
loop: boolean;
onComplete?: () => void; // Optional callback
}
// ๐ฎ Base game object interface
interface GameObject {
id: string;
name: string;
position: Vector3D;
rotation: number; // degrees
scale: Vector2D;
visible: boolean;
layer: number;
tags: Set<string>;
}
// ๐โโ๏ธ Moveable game object
interface Moveable {
velocity: Vector3D;
acceleration: Vector3D;
maxSpeed: number;
move(delta: number): void;
}
// ๐ฅ Damageable interface
interface Damageable {
health: number;
maxHealth: number;
armor: number;
invulnerable: boolean;
takeDamage(amount: number, source?: GameObject): void;
heal(amount: number): void;
onDeath?: () => void;
}
// ๐ฏ Interactive interface
interface Interactive {
interactionRadius: number;
canInteract: boolean;
interact(actor: GameObject): void;
getInteractionPrompt(): string;
}
// ๐ค Character interface combining multiple interfaces
interface Character extends GameObject, Moveable, Damageable {
level: number;
experience: number;
stats: CharacterStats;
abilities: Ability[];
inventory: InventoryItem[];
currentAnimation?: Animation;
}
// ๐ Character stats interface
interface CharacterStats {
strength: number;
agility: number;
intelligence: number;
vitality: number;
luck: number;
}
// โจ Ability interface
interface Ability {
id: string;
name: string;
description: string;
icon: string;
cooldown: number; // seconds
currentCooldown: number;
manaCost: number;
range: number;
damage?: number;
healing?: number;
effects: Effect[];
cast(caster: Character, target?: GameObject): void;
}
// ๐ Effect interface
interface Effect {
type: 'buff' | 'debuff' | 'damage' | 'heal';
duration: number; // seconds
value: number;
stackable: boolean;
icon: string;
apply(target: Character): void;
remove(target: Character): void;
}
// ๐ Inventory item interface
interface InventoryItem {
id: string;
name: string;
description: string;
icon: string;
stackable: boolean;
quantity: number;
weight: number;
value: number; // gold value
rarity: 'common' | 'uncommon' | 'rare' | 'epic' | 'legendary';
onUse?: (user: Character) => void;
}
// โ๏ธ Weapon interface extending inventory item
interface Weapon extends InventoryItem {
damage: number;
attackSpeed: number;
range: number;
weaponType: 'sword' | 'bow' | 'staff' | 'dagger';
requirements: {
level?: number;
strength?: number;
agility?: number;
};
enchantments?: Enchantment[];
}
// โจ Enchantment interface
interface Enchantment {
name: string;
tier: number;
effect: Effect;
description: string;
}
// ๐ Game world interface
interface GameWorld {
name: string;
dimensions: Vector3D;
gravity: number;
timeScale: number;
weather: Weather;
objects: Map<string, GameObject>;
update(deltaTime: number): void;
render(): void;
}
// ๐ฆ๏ธ Weather interface
interface Weather {
type: 'sunny' | 'rainy' | 'stormy' | 'snowy' | 'foggy';
intensity: number; // 0-1
windDirection: Vector2D;
windSpeed: number;
temperature: number;
}
// ๐ฎ Player character implementation
class Player implements Character {
id: string;
name: string;
position: Vector3D;
rotation: number = 0;
scale: Vector2D = { x: 1, y: 1 };
visible: boolean = true;
layer: number = 1;
tags: Set<string> = new Set(['player', 'character']);
velocity: Vector3D = { x: 0, y: 0, z: 0 };
acceleration: Vector3D = { x: 0, y: 0, z: 0 };
maxSpeed: number = 5;
health: number;
maxHealth: number;
armor: number = 0;
invulnerable: boolean = false;
level: number = 1;
experience: number = 0;
stats: CharacterStats;
abilities: Ability[] = [];
inventory: InventoryItem[] = [];
currentAnimation?: Animation;
constructor(name: string, position: Vector3D) {
this.id = `PLAYER_${Date.now()}`;
this.name = name;
this.position = position;
this.maxHealth = 100;
this.health = this.maxHealth;
this.stats = {
strength: 10,
agility: 10,
intelligence: 10,
vitality: 10,
luck: 5
};
console.log(`๐ฎ Player ${name} created at position (${position.x}, ${position.y}, ${position.z})`);
}
move(delta: number): void {
// Apply acceleration to velocity
this.velocity.x += this.acceleration.x * delta;
this.velocity.y += this.acceleration.y * delta;
this.velocity.z += this.acceleration.z * delta;
// Clamp to max speed
const speed = Math.sqrt(
this.velocity.x ** 2 +
this.velocity.y ** 2 +
this.velocity.z ** 2
);
if (speed > this.maxSpeed) {
const scale = this.maxSpeed / speed;
this.velocity.x *= scale;
this.velocity.y *= scale;
this.velocity.z *= scale;
}
// Update position
this.position.x += this.velocity.x * delta;
this.position.y += this.velocity.y * delta;
this.position.z += this.velocity.z * delta;
}
takeDamage(amount: number, source?: GameObject): void {
if (this.invulnerable) {
console.log(`๐ก๏ธ ${this.name} is invulnerable!`);
return;
}
const actualDamage = Math.max(0, amount - this.armor);
this.health = Math.max(0, this.health - actualDamage);
console.log(`๐ฅ ${this.name} takes ${actualDamage} damage! Health: ${this.health}/${this.maxHealth}`);
if (this.health === 0 && this.onDeath) {
this.onDeath();
}
}
heal(amount: number): void {
const oldHealth = this.health;
this.health = Math.min(this.maxHealth, this.health + amount);
const healed = this.health - oldHealth;
if (healed > 0) {
console.log(`๐ ${this.name} healed for ${healed}! Health: ${this.health}/${this.maxHealth}`);
}
}
onDeath(): void {
console.log(`โ ๏ธ ${this.name} has been defeated!`);
}
addToInventory(item: InventoryItem): boolean {
if (item.stackable) {
const existing = this.inventory.find(i => i.id === item.id);
if (existing) {
existing.quantity += item.quantity;
console.log(`๐ฆ Added ${item.quantity} ${item.name} (total: ${existing.quantity})`);
return true;
}
}
this.inventory.push(item);
console.log(`๐ Added ${item.name} to inventory`);
return true;
}
equipWeapon(weapon: Weapon): boolean {
// Check requirements
if (weapon.requirements.level && this.level < weapon.requirements.level) {
console.log(`โ Need level ${weapon.requirements.level} to equip ${weapon.name}`);
return false;
}
if (weapon.requirements.strength && this.stats.strength < weapon.requirements.strength) {
console.log(`โ Need ${weapon.requirements.strength} strength to equip ${weapon.name}`);
return false;
}
console.log(`โ๏ธ Equipped ${weapon.name}!`);
return true;
}
}
// ๐ช NPC with interactive interface
class Merchant implements GameObject, Interactive {
id: string;
name: string;
position: Vector3D;
rotation: number = 0;
scale: Vector2D = { x: 1, y: 1 };
visible: boolean = true;
layer: number = 1;
tags: Set<string> = new Set(['npc', 'merchant']);
interactionRadius: number = 2;
canInteract: boolean = true;
private inventory: InventoryItem[] = [];
constructor(name: string, position: Vector3D) {
this.id = `MERCHANT_${Date.now()}`;
this.name = name;
this.position = position;
// Stock some items
this.stockInventory();
}
private stockInventory(): void {
// Create a weapon
const ironSword: Weapon = {
id: 'WEAPON_IRON_SWORD',
name: 'Iron Sword',
description: 'A sturdy iron sword',
icon: 'iron-sword.png',
stackable: false,
quantity: 1,
weight: 3,
value: 100,
rarity: 'common',
damage: 10,
attackSpeed: 1.5,
range: 1.5,
weaponType: 'sword',
requirements: {
level: 1,
strength: 5
}
};
// Create a potion
const healthPotion: InventoryItem = {
id: 'ITEM_HEALTH_POTION',
name: 'Health Potion',
description: 'Restores 50 HP',
icon: 'health-potion.png',
stackable: true,
quantity: 10,
weight: 0.5,
value: 50,
rarity: 'common',
onUse: (user: Character) => {
user.heal(50);
console.log(`๐งช ${user.name} drinks a health potion!`);
}
};
this.inventory.push(ironSword, healthPotion);
}
interact(actor: GameObject): void {
if (actor instanceof Player) {
console.log(`๐ช ${this.name}: "Welcome, ${actor.name}! Care to see my wares?"`);
this.showInventory();
}
}
getInteractionPrompt(): string {
return `Talk to ${this.name} (E)`;
}
private showInventory(): void {
console.log('\n๐ฆ Merchant Inventory:');
this.inventory.forEach(item => {
const rarityEmoji = {
common: 'โช',
uncommon: '๐ข',
rare: '๐ต',
epic: '๐ฃ',
legendary: '๐ก'
};
console.log(`${rarityEmoji[item.rarity]} ${item.name} - ${item.value} gold`);
});
}
}
// ๐ฎ Demo the game system
console.log('๐ฎ GAME SYSTEM DEMO ๐ฎ\n');
// Create player
const player = new Player('Hero', { x: 0, y: 0, z: 0 });
// Create merchant
const merchant = new Merchant('Grizelda the Trader', { x: 5, y: 0, z: 0 });
// Test damage and healing
player.takeDamage(30);
player.heal(20);
// Test inventory
const magicRing: InventoryItem = {
id: 'ITEM_MAGIC_RING',
name: 'Ring of Power',
description: 'Increases all stats by 5',
icon: 'magic-ring.png',
stackable: false,
quantity: 1,
weight: 0.1,
value: 500,
rarity: 'rare'
};
player.addToInventory(magicRing);
// Create and equip a weapon
const legendaryStaff: Weapon = {
id: 'WEAPON_LEGENDARY_STAFF',
name: 'Staff of the Archmage',
description: 'A powerful magical staff',
icon: 'archmage-staff.png',
stackable: false,
quantity: 1,
weight: 2,
value: 5000,
rarity: 'legendary',
damage: 50,
attackSpeed: 0.8,
range: 5,
weaponType: 'staff',
requirements: {
level: 10,
intelligence: 20
},
enchantments: [
{
name: 'Frost Damage',
tier: 3,
effect: {
type: 'damage',
duration: 3,
value: 10,
stackable: false,
icon: 'frost.png',
apply: (target) => console.log(`โ๏ธ ${target.name} is frozen!`),
remove: (target) => console.log(`๐ฅ ${target.name} thaws out`)
},
description: 'Deals frost damage over time'
}
]
};
// Try to equip (will fail due to level requirement)
player.equipWeapon(legendaryStaff);
// Check if player can interact with merchant
const distance = Math.sqrt(
Math.pow(player.position.x - merchant.position.x, 2) +
Math.pow(player.position.y - merchant.position.y, 2)
);
if (distance <= merchant.interactionRadius) {
console.log(`\n๐ฌ ${merchant.getInteractionPrompt()}`);
merchant.interact(player);
} else {
console.log(`\n๐ถ Too far from merchant (distance: ${distance.toFixed(1)})`);
}
๐ Advanced Concepts
๐งโโ๏ธ Advanced Topic 1: Index Signatures and Mapped Types
Create flexible interfaces with dynamic properties:
// ๐ Index signature for dynamic properties
interface Dictionary<T> {
[key: string]: T;
}
// ๐ Analytics data with flexible metrics
interface AnalyticsEvent {
eventName: string;
timestamp: Date;
userId: string;
properties: Dictionary<string | number | boolean>;
metadata?: Dictionary<any>;
}
// ๐ฏ Strongly typed configuration
interface AppConfig {
appName: string;
version: string;
features: {
[featureName: string]: {
enabled: boolean;
config?: Dictionary<any>;
};
};
}
// ๐ API response with dynamic fields
interface ApiResponse<T> {
data: T;
meta: {
timestamp: Date;
requestId: string;
[key: string]: any; // Additional metadata
};
errors?: Array<{
code: string;
message: string;
field?: string;
}>;
}
// ๐ Form data with validation
interface FormField {
value: any;
touched: boolean;
errors: string[];
validators: Array<(value: any) => string | null>;
}
interface Form {
fields: {
[fieldName: string]: FormField;
};
isValid: boolean;
isDirty: boolean;
}
// ๐จ Theme system with nested properties
interface Theme {
colors: {
primary: string;
secondary: string;
[colorName: string]: string;
};
spacing: {
small: number;
medium: number;
large: number;
[size: string]: number;
};
components: {
[componentName: string]: {
[property: string]: string | number;
};
};
}
// ๐ ๏ธ Implementation examples
class AnalyticsService {
private events: AnalyticsEvent[] = [];
track(eventName: string, userId: string, properties: Dictionary<any>): void {
const event: AnalyticsEvent = {
eventName,
timestamp: new Date(),
userId,
properties
};
this.events.push(event);
console.log(`๐ Tracked: ${eventName} for user ${userId}`);
}
// ๐ Get events with specific property
getEventsWithProperty(propertyName: string): AnalyticsEvent[] {
return this.events.filter(event =>
propertyName in event.properties
);
}
}
// ๐จ Theme implementation
const darkTheme: Theme = {
colors: {
primary: '#007bff',
secondary: '#6c757d',
background: '#1a1a1a',
text: '#ffffff',
error: '#dc3545',
success: '#28a745'
},
spacing: {
small: 8,
medium: 16,
large: 24,
extraLarge: 32
},
components: {
button: {
borderRadius: 4,
padding: 12,
fontSize: 16
},
card: {
borderRadius: 8,
shadowBlur: 10,
padding: 16
}
}
};
// ๐ง Form builder with dynamic fields
class FormBuilder {
createForm(fieldConfigs: Dictionary<Partial<FormField>>): Form {
const fields: Dictionary<FormField> = {};
Object.entries(fieldConfigs).forEach(([name, config]) => {
fields[name] = {
value: config.value ?? '',
touched: config.touched ?? false,
errors: config.errors ?? [],
validators: config.validators ?? []
};
});
return {
fields,
isValid: this.validateForm(fields),
isDirty: false
};
}
private validateForm(fields: Dictionary<FormField>): boolean {
return Object.values(fields).every(field =>
field.errors.length === 0
);
}
}
// ๐ฎ Demo
const analytics = new AnalyticsService();
// Track various events
analytics.track('page_view', 'USER_123', {
page: '/products',
referrer: 'google.com',
device: 'mobile',
sessionDuration: 145
});
analytics.track('purchase', 'USER_456', {
productId: 'PROD_789',
amount: 99.99,
currency: 'USD',
paymentMethod: 'credit_card',
couponUsed: true
});
// Form example
const formBuilder = new FormBuilder();
const loginForm = formBuilder.createForm({
email: {
value: '',
validators: [
(value) => !value ? 'Email is required' : null,
(value) => !value.includes('@') ? 'Invalid email' : null
]
},
password: {
value: '',
validators: [
(value) => !value ? 'Password is required' : null,
(value) => value.length < 8 ? 'Min 8 characters' : null
]
}
});
console.log('๐ Form created with fields:', Object.keys(loginForm.fields));
๐๏ธ Advanced Topic 2: Discriminated Unions with Interfaces
Create type-safe state machines and variant types:
// ๐ฆ Network request states
interface RequestPending {
status: 'pending';
startedAt: Date;
}
interface RequestSuccess<T> {
status: 'success';
data: T;
completedAt: Date;
cached: boolean;
}
interface RequestError {
status: 'error';
error: Error;
code: string;
retryCount: number;
canRetry: boolean;
}
interface RequestIdle {
status: 'idle';
}
// Union type of all states
type RequestState<T> =
| RequestIdle
| RequestPending
| RequestSuccess<T>
| RequestError;
// ๐ฏ Type-safe state handling
function handleRequestState<T>(state: RequestState<T>): string {
switch (state.status) {
case 'idle':
return '๐ค Waiting to start...';
case 'pending':
const elapsed = Date.now() - state.startedAt.getTime();
return `โณ Loading... (${Math.round(elapsed / 1000)}s)`;
case 'success':
return `โ
Success! ${state.cached ? '(from cache)' : '(fresh data)'}`;
case 'error':
return `โ Error: ${state.error.message} ${state.canRetry ? '(retrying...)' : ''}`;
}
}
// ๐ฎ Game state variants
interface MenuState {
type: 'menu';
selectedOption: number;
options: string[];
}
interface PlayingState {
type: 'playing';
level: number;
score: number;
lives: number;
isPaused: boolean;
}
interface GameOverState {
type: 'gameOver';
finalScore: number;
highScore: number;
newRecord: boolean;
}
interface LoadingState {
type: 'loading';
progress: number;
message: string;
}
type GameState = MenuState | PlayingState | GameOverState | LoadingState;
// ๐ฎ Game state machine
class GameStateMachine {
private currentState: GameState;
private stateHistory: GameState[] = [];
constructor() {
this.currentState = {
type: 'menu',
selectedOption: 0,
options: ['New Game', 'Continue', 'Options', 'Quit']
};
}
transition(newState: GameState): void {
console.log(`๐ State transition: ${this.currentState.type} โ ${newState.type}`);
this.stateHistory.push(this.currentState);
this.currentState = newState;
this.handleStateChange();
}
private handleStateChange(): void {
switch (this.currentState.type) {
case 'menu':
console.log('๐ฎ Main Menu');
this.currentState.options.forEach((opt, i) => {
const selected = i === this.currentState.selectedOption ? 'โถ๏ธ' : ' ';
console.log(`${selected} ${opt}`);
});
break;
case 'loading':
console.log(`โณ Loading: ${this.currentState.message} (${this.currentState.progress}%)`);
break;
case 'playing':
console.log(`๐ฎ Playing Level ${this.currentState.level}`);
console.log(` Score: ${this.currentState.score} | Lives: ${'โค๏ธ'.repeat(this.currentState.lives)}`);
if (this.currentState.isPaused) {
console.log(' โธ๏ธ PAUSED');
}
break;
case 'gameOver':
console.log('๐ GAME OVER');
console.log(` Final Score: ${this.currentState.finalScore}`);
if (this.currentState.newRecord) {
console.log(' ๐ NEW HIGH SCORE!');
}
break;
}
}
// Type-safe state updates
updateState<K extends GameState['type']>(
type: K,
updater: (state: Extract<GameState, { type: K }>) => void
): void {
if (this.currentState.type === type) {
updater(this.currentState as any);
this.handleStateChange();
}
}
}
// ๐ Data processing pipeline states
interface DataSource {
kind: 'source';
url: string;
headers?: Dictionary<string>;
}
interface DataTransform {
kind: 'transform';
operation: 'filter' | 'map' | 'reduce' | 'sort';
config: any;
}
interface DataSink {
kind: 'sink';
destination: 'database' | 'file' | 'api';
options: Dictionary<any>;
}
type PipelineStage = DataSource | DataTransform | DataSink;
interface Pipeline {
name: string;
stages: PipelineStage[];
schedule?: string;
enabled: boolean;
}
// ๐ง Pipeline processor
class PipelineProcessor {
async processPipeline(pipeline: Pipeline): Promise<void> {
console.log(`๐ Starting pipeline: ${pipeline.name}`);
for (const stage of pipeline.stages) {
switch (stage.kind) {
case 'source':
console.log(`๐ฅ Fetching data from: ${stage.url}`);
// Fetch data logic
break;
case 'transform':
console.log(`๐ Applying ${stage.operation} transformation`);
// Transform data logic
break;
case 'sink':
console.log(`๐ค Writing data to: ${stage.destination}`);
// Write data logic
break;
}
}
console.log(`โ
Pipeline ${pipeline.name} completed`);
}
}
// ๐ฎ Demo discriminated unions
const game = new GameStateMachine();
// Start game
game.transition({
type: 'loading',
progress: 0,
message: 'Initializing game engine...'
});
// Simulate loading progress
setTimeout(() => {
game.transition({
type: 'loading',
progress: 50,
message: 'Loading assets...'
});
}, 1000);
setTimeout(() => {
game.transition({
type: 'playing',
level: 1,
score: 0,
lives: 3,
isPaused: false
});
// Update score while playing
game.updateState('playing', state => {
state.score += 100;
});
}, 2000);
// Data pipeline example
const etlPipeline: Pipeline = {
name: 'User Analytics ETL',
enabled: true,
stages: [
{
kind: 'source',
url: 'https://api.example.com/users',
headers: {
'Authorization': 'Bearer token'
}
},
{
kind: 'transform',
operation: 'filter',
config: { active: true }
},
{
kind: 'transform',
operation: 'map',
config: { fields: ['id', 'name', 'email'] }
},
{
kind: 'sink',
destination: 'database',
options: {
table: 'analytics_users',
upsert: true
}
}
]
};
const processor = new PipelineProcessor();
// processor.processPipeline(etlPipeline);
โ ๏ธ Common Pitfalls and Solutions
๐ฑ Pitfall 1: Interface vs Type Confusion
// โ Using type when interface would be better
type UserType = {
name: string;
age: number;
};
// โ
Use interface for object shapes
interface UserInterface {
name: string;
age: number;
}
// โ
Interfaces can be extended
interface AdminUser extends UserInterface {
permissions: string[];
}
// โ
Interfaces can be merged (declaration merging)
interface UserInterface {
email?: string; // This gets merged!
}
// โ Types can't be merged
type UserType = {
email?: string; // Error: Duplicate identifier
};
๐คฏ Pitfall 2: Optional vs Undefined
// โ Confusing optional with undefined
interface BadConfig {
apiUrl: string | undefined; // Must provide, but can be undefined
timeout?: number; // Truly optional
}
// This is required but can be undefined
const config1: BadConfig = {
apiUrl: undefined,
// timeout is optional, so we can omit it
};
// โ This errors - apiUrl is required!
// const config2: BadConfig = {
// timeout: 5000
// };
// โ
Clear intent
interface GoodConfig {
apiUrl: string; // Required and must have value
timeout?: number; // Optional
retry?: boolean; // Optional
}
๐ Pitfall 3: Excess Property Checks
interface Point {
x: number;
y: number;
}
// โ Excess property check fails
const point1: Point = {
x: 10,
y: 20,
z: 30 // Error: 'z' does not exist in type 'Point'
};
// โ
Ways to handle extra properties
// 1. Use index signature
interface FlexiblePoint {
x: number;
y: number;
[key: string]: number; // Allow any additional number properties
}
// 2. Type assertion
const point2 = {
x: 10,
y: 20,
z: 30
} as Point; // Works but loses type safety for 'z'
// 3. Separate variable
const point3Data = { x: 10, y: 20, z: 30 };
const point3: Point = point3Data; // Works!
๐ ๏ธ Best Practices
- ๐ฏ Use Interfaces for Object Shapes: Prefer interfaces over type aliases for objects
- ๐ Document with JSDoc: Add comments to interface properties
- ๐ง Keep Interfaces Focused: Single responsibility principle applies
- โป๏ธ Extend and Compose: Build complex interfaces from simple ones
- ๐ก๏ธ Use Readonly When Appropriate: Prevent unwanted mutations
๐งช Hands-On Exercise
๐ฏ Challenge: Build a Task Management System
Create a comprehensive task management system with interfaces:
๐ Requirements:
- โ Task interface with title, description, status, priority
- ๐ค User interface with roles and permissions
- ๐ Project interface containing multiple tasks
- ๐ Notification system for task updates
- ๐ Analytics interface for tracking productivity
๐ Bonus Points:
- Add task dependencies
- Implement recurring tasks
- Create a comment system
- Add time tracking
๐ก Solution
๐ Click to see solution
// ๐ค User interfaces
interface User {
id: string;
name: string;
email: string;
avatar?: string;
role: UserRole;
permissions: Set<Permission>;
preferences: UserPreferences;
createdAt: Date;
lastLogin: Date;
}
interface UserRole {
id: string;
name: 'admin' | 'manager' | 'member' | 'guest';
level: number;
}
type Permission =
| 'create:task'
| 'edit:task'
| 'delete:task'
| 'assign:task'
| 'create:project'
| 'edit:project'
| 'delete:project'
| 'view:analytics';
interface UserPreferences {
theme: 'light' | 'dark' | 'auto';
notifications: {
email: boolean;
push: boolean;
slack: boolean;
};
language: string;
timezone: string;
}
// ๐ Task interfaces
interface Task {
id: string;
title: string;
description: string;
status: TaskStatus;
priority: TaskPriority;
assignee?: User;
reporter: User;
projectId: string;
tags: string[];
dueDate?: Date;
estimatedHours?: number;
actualHours?: number;
dependencies: string[]; // Task IDs
attachments: Attachment[];
comments: Comment[];
createdAt: Date;
updatedAt: Date;
completedAt?: Date;
}
type TaskStatus =
| 'backlog'
| 'todo'
| 'in_progress'
| 'review'
| 'done'
| 'cancelled';
type TaskPriority = 'low' | 'medium' | 'high' | 'urgent';
interface Attachment {
id: string;
filename: string;
url: string;
size: number;
mimeType: string;
uploadedBy: User;
uploadedAt: Date;
}
interface Comment {
id: string;
content: string;
author: User;
createdAt: Date;
editedAt?: Date;
mentions: User[];
reactions: Reaction[];
}
interface Reaction {
emoji: string;
users: User[];
}
// ๐ Project interfaces
interface Project {
id: string;
name: string;
description: string;
code: string; // Project code like "PROJ-123"
status: 'planning' | 'active' | 'on_hold' | 'completed' | 'cancelled';
owner: User;
members: ProjectMember[];
startDate: Date;
targetDate?: Date;
actualEndDate?: Date;
budget?: number;
tags: string[];
settings: ProjectSettings;
}
interface ProjectMember {
user: User;
role: 'owner' | 'admin' | 'member' | 'viewer';
joinedAt: Date;
}
interface ProjectSettings {
isPrivate: boolean;
allowGuestAccess: boolean;
requireApproval: boolean;
defaultTaskStatus: TaskStatus;
taskPrefix: string;
}
// ๐ Recurring task interfaces
interface RecurringTask extends Omit<Task, 'id' | 'status'> {
recurrenceRule: RecurrenceRule;
instances: Task[];
nextOccurrence?: Date;
}
interface RecurrenceRule {
frequency: 'daily' | 'weekly' | 'monthly' | 'yearly';
interval: number;
daysOfWeek?: number[]; // 0-6 for weekly
dayOfMonth?: number; // 1-31 for monthly
endDate?: Date;
maxOccurrences?: number;
}
// ๐ Notification interfaces
interface Notification {
id: string;
type: NotificationType;
recipient: User;
title: string;
message: string;
data: NotificationData;
read: boolean;
createdAt: Date;
readAt?: Date;
}
type NotificationType =
| 'task:assigned'
| 'task:mentioned'
| 'task:completed'
| 'task:overdue'
| 'project:added'
| 'comment:new'
| 'comment:mention';
interface NotificationData {
taskId?: string;
projectId?: string;
commentId?: string;
userId?: string;
[key: string]: any;
}
// ๐ Analytics interfaces
interface TaskAnalytics {
totalTasks: number;
completedTasks: number;
overdueTasks: number;
averageCompletionTime: number; // hours
tasksByStatus: Record<TaskStatus, number>;
tasksByPriority: Record<TaskPriority, number>;
velocityTrend: VelocityPoint[];
}
interface VelocityPoint {
date: Date;
completed: number;
created: number;
}
interface UserProductivity {
userId: string;
tasksCompleted: number;
averageTaskTime: number;
punctualityRate: number; // % of tasks completed on time
currentStreak: number; // days
longestStreak: number;
}
interface ProjectHealth {
projectId: string;
healthScore: number; // 0-100
onTrack: boolean;
riskFactors: string[];
burndownChart: BurndownPoint[];
teamUtilization: number; // percentage
}
interface BurndownPoint {
date: Date;
remainingTasks: number;
idealRemaining: number;
}
// ๐ ๏ธ Task management service
class TaskManagementService {
private tasks: Map<string, Task> = new Map();
private projects: Map<string, Project> = new Map();
private users: Map<string, User> = new Map();
private notifications: Notification[] = [];
// ๐ Create a new task
createTask(taskData: Omit<Task, 'id' | 'createdAt' | 'updatedAt' | 'comments'>): Task {
const task: Task = {
...taskData,
id: `TASK_${Date.now()}`,
createdAt: new Date(),
updatedAt: new Date(),
comments: []
};
this.tasks.set(task.id, task);
// Send notification if assigned
if (task.assignee) {
this.sendNotification({
type: 'task:assigned',
recipient: task.assignee,
title: 'New Task Assigned',
message: `You've been assigned to "${task.title}"`,
data: { taskId: task.id, projectId: task.projectId }
});
}
console.log(`โ
Created task: ${task.title} (${task.id})`);
return task;
}
// ๐ Update task status
updateTaskStatus(taskId: string, newStatus: TaskStatus, user: User): void {
const task = this.tasks.get(taskId);
if (!task) throw new Error(`Task ${taskId} not found`);
const oldStatus = task.status;
task.status = newStatus;
task.updatedAt = new Date();
if (newStatus === 'done') {
task.completedAt = new Date();
console.log(`โ
Task completed: ${task.title}`);
// Check dependencies
this.checkDependentTasks(taskId);
}
console.log(`๐ Task ${task.title}: ${oldStatus} โ ${newStatus}`);
}
// ๐ Check dependent tasks
private checkDependentTasks(completedTaskId: string): void {
this.tasks.forEach(task => {
if (task.dependencies.includes(completedTaskId)) {
const allDependenciesComplete = task.dependencies.every(depId => {
const depTask = this.tasks.get(depId);
return depTask?.status === 'done';
});
if (allDependenciesComplete && task.status === 'backlog') {
task.status = 'todo';
console.log(`๐ Task "${task.title}" is now unblocked!`);
}
}
});
}
// ๐ฌ Add comment to task
addComment(taskId: string, author: User, content: string): Comment {
const task = this.tasks.get(taskId);
if (!task) throw new Error(`Task ${taskId} not found`);
const comment: Comment = {
id: `COMMENT_${Date.now()}`,
content,
author,
createdAt: new Date(),
mentions: this.extractMentions(content),
reactions: []
};
task.comments.push(comment);
task.updatedAt = new Date();
// Notify mentioned users
comment.mentions.forEach(user => {
this.sendNotification({
type: 'comment:mention',
recipient: user,
title: 'You were mentioned',
message: `${author.name} mentioned you in "${task.title}"`,
data: { taskId, commentId: comment.id }
});
});
console.log(`๐ฌ Comment added to task ${task.title}`);
return comment;
}
// ๐ฅ Extract mentions from text
private extractMentions(text: string): User[] {
const mentionPattern = /@(\w+)/g;
const mentions: User[] = [];
let match;
while ((match = mentionPattern.exec(text)) !== null) {
const username = match[1];
// In real app, would look up user by username
// For demo, we'll skip this
}
return mentions;
}
// ๐ Send notification
private sendNotification(data: Omit<Notification, 'id' | 'read' | 'createdAt'>): void {
const notification: Notification = {
...data,
id: `NOTIF_${Date.now()}`,
read: false,
createdAt: new Date()
};
this.notifications.push(notification);
console.log(`๐ Notification sent to ${data.recipient.name}: ${data.title}`);
}
// ๐ Get project analytics
getProjectAnalytics(projectId: string): TaskAnalytics {
const projectTasks = Array.from(this.tasks.values())
.filter(task => task.projectId === projectId);
const now = new Date();
const analytics: TaskAnalytics = {
totalTasks: projectTasks.length,
completedTasks: projectTasks.filter(t => t.status === 'done').length,
overdueTasks: projectTasks.filter(t =>
t.dueDate && t.dueDate < now && t.status !== 'done'
).length,
averageCompletionTime: this.calculateAverageCompletionTime(projectTasks),
tasksByStatus: this.groupTasksByStatus(projectTasks),
tasksByPriority: this.groupTasksByPriority(projectTasks),
velocityTrend: this.calculateVelocityTrend(projectTasks)
};
return analytics;
}
private calculateAverageCompletionTime(tasks: Task[]): number {
const completedTasks = tasks.filter(t => t.completedAt);
if (completedTasks.length === 0) return 0;
const totalTime = completedTasks.reduce((sum, task) => {
const time = task.completedAt!.getTime() - task.createdAt.getTime();
return sum + time;
}, 0);
return totalTime / completedTasks.length / (1000 * 60 * 60); // Convert to hours
}
private groupTasksByStatus(tasks: Task[]): Record<TaskStatus, number> {
const groups: Record<TaskStatus, number> = {
backlog: 0,
todo: 0,
in_progress: 0,
review: 0,
done: 0,
cancelled: 0
};
tasks.forEach(task => {
groups[task.status]++;
});
return groups;
}
private groupTasksByPriority(tasks: Task[]): Record<TaskPriority, number> {
const groups: Record<TaskPriority, number> = {
low: 0,
medium: 0,
high: 0,
urgent: 0
};
tasks.forEach(task => {
groups[task.priority]++;
});
return groups;
}
private calculateVelocityTrend(tasks: Task[]): VelocityPoint[] {
// Simplified - in real app would calculate daily/weekly velocity
return [];
}
}
// ๐ฎ Demo the task management system
console.log('๐ TASK MANAGEMENT SYSTEM DEMO ๐\n');
const service = new TaskManagementService();
// Create users
const alice: User = {
id: 'USER_1',
name: 'Alice Developer',
email: '[email protected]',
role: { id: 'ROLE_1', name: 'member', level: 2 },
permissions: new Set(['create:task', 'edit:task']),
preferences: {
theme: 'dark',
notifications: { email: true, push: true, slack: false },
language: 'en',
timezone: 'UTC'
},
createdAt: new Date(),
lastLogin: new Date()
};
const bob: User = {
id: 'USER_2',
name: 'Bob Manager',
email: '[email protected]',
role: { id: 'ROLE_2', name: 'manager', level: 3 },
permissions: new Set(['create:task', 'edit:task', 'delete:task', 'assign:task']),
preferences: {
theme: 'light',
notifications: { email: true, push: false, slack: true },
language: 'en',
timezone: 'UTC'
},
createdAt: new Date(),
lastLogin: new Date()
};
// Create tasks
const task1 = service.createTask({
title: 'Implement user authentication',
description: 'Add JWT-based authentication to the API',
status: 'todo',
priority: 'high',
assignee: alice,
reporter: bob,
projectId: 'PROJ_1',
tags: ['backend', 'security'],
dueDate: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), // 1 week
estimatedHours: 16,
dependencies: [],
attachments: []
});
const task2 = service.createTask({
title: 'Create login UI',
description: 'Design and implement the login page',
status: 'backlog',
priority: 'medium',
assignee: alice,
reporter: bob,
projectId: 'PROJ_1',
tags: ['frontend', 'ui'],
estimatedHours: 8,
dependencies: [task1.id], // Depends on auth implementation
attachments: []
});
// Update task status
service.updateTaskStatus(task1.id, 'in_progress', alice);
// Add comment
service.addComment(
task1.id,
alice,
'Started working on this. Using JWT with refresh tokens for better security.'
);
// Get analytics
const analytics = service.getProjectAnalytics('PROJ_1');
console.log('\n๐ Project Analytics:');
console.log(` Total tasks: ${analytics.totalTasks}`);
console.log(` Completed: ${analytics.completedTasks}`);
console.log(` Overdue: ${analytics.overdueTasks}`);
console.log(` Task distribution:`, analytics.tasksByStatus);
๐ Key Takeaways
Youโve mastered TypeScript interfaces! Hereโs what you can now do:
- โ Define object shapes with precision ๐ฏ
- โ Use optional and readonly properties effectively ๐
- โ Extend and compose interfaces for complex types ๐๏ธ
- โ Create type-safe contracts for your code ๐
- โ Build discriminated unions for state management ๐ฆ
Remember: Interfaces are the foundation of type-safe TypeScript - use them everywhere! ๐
๐ค Next Steps
Congratulations! ๐ Youโve mastered TypeScript interfaces!
Hereโs what to do next:
- ๐ป Practice with the task management exercise above
- ๐๏ธ Design interfaces for your own projects
- ๐ Move on to our next tutorial: Interface vs Type Alias: When to Use Each
- ๐ Experiment with advanced interface patterns!
Remember: Great TypeScript code starts with well-designed interfaces. Keep defining, keep building, and create amazing type-safe applications! ๐
Happy coding! ๐๐โจ