+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Part 59 of 354

๐Ÿ”Œ Generic Interfaces: Parameterized Contracts

Master generic interfaces in TypeScript to create flexible, reusable contracts that adapt to any type ๐Ÿš€

๐Ÿš€Intermediate
30 min read

Prerequisites

  • Understanding of TypeScript interfaces ๐Ÿ“
  • Knowledge of generics basics ๐Ÿ”
  • Familiarity with type parameters ๐Ÿ’ป

What you'll learn

  • Create powerful generic interface contracts ๐ŸŽฏ
  • Build type-safe API definitions ๐Ÿ—๏ธ
  • Design flexible interface hierarchies ๐Ÿ›ก๏ธ
  • Implement advanced generic patterns โœจ

๐ŸŽฏ Introduction

Welcome to the world of generic interfaces! ๐ŸŽ‰ In this guide, weโ€™ll explore how to create flexible, parameterized contracts that define the shape of your data while adapting to any type.

Youโ€™ll discover how generic interfaces are like universal plug adapters ๐Ÿ”Œ - they provide a consistent connection interface while working with different types! Whether youโ€™re designing APIs ๐ŸŒ, building libraries ๐Ÿ“š, or creating complex type hierarchies ๐Ÿ—๏ธ, mastering generic interfaces is crucial for professional TypeScript development.

By the end of this tutorial, youโ€™ll be confidently creating generic interfaces that solve real-world problems elegantly! Letโ€™s build some powerful contracts! ๐ŸŠโ€โ™‚๏ธ

๐Ÿ“š Understanding Generic Interfaces

๐Ÿค” Why Generic Interfaces?

Generic interfaces allow you to create flexible contracts that work with multiple types while maintaining type safety:

// โŒ Without generics - Limited flexibility
interface StringContainer {
  value: string;
  getValue(): string;
  setValue(value: string): void;
}

interface NumberContainer {
  value: number;
  getValue(): number;
  setValue(value: number): void;
}

// Duplicate interfaces for each type! ๐Ÿ˜ข

// โœ… With generics - One interface, infinite possibilities
interface Container<T> {
  value: T;
  getValue(): T;
  setValue(value: T): void;
}

// Implementation for any type
class Box<T> implements Container<T> {
  constructor(public value: T) {}
  
  getValue(): T {
    return this.value;
  }
  
  setValue(value: T): void {
    this.value = value;
  }
}

const stringBox = new Box<string>('hello');
const numberBox = new Box<number>(42);
const objectBox = new Box<{ name: string }>({ name: 'Alice' });

๐Ÿ’ก Generic Interface Syntax

Understanding the structure and patterns:

// ๐ŸŽฏ Basic generic interface
interface Pair<T, U> {
  first: T;
  second: U;
}

const coordinate: Pair<number, number> = { first: 10, second: 20 };
const keyValue: Pair<string, boolean> = { first: 'active', second: true };

// ๐Ÿ—๏ธ Generic methods in interfaces
interface Collection<T> {
  items: T[];
  add(item: T): void;
  remove(item: T): boolean;
  find(predicate: (item: T) => boolean): T | undefined;
  map<U>(transform: (item: T) => U): U[];
}

// ๐Ÿ”ง Multiple type parameters with constraints
interface Dictionary<K extends string | number, V> {
  get(key: K): V | undefined;
  set(key: K, value: V): void;
  has(key: K): boolean;
  delete(key: K): boolean;
  clear(): void;
  size(): number;
}

// ๐ŸŽจ Extending generic interfaces
interface ReadonlyDictionary<K extends string | number, V> 
  extends Dictionary<K, V> {
  readonly [key: string]: V;
  set: never; // Disable set method
  delete: never; // Disable delete method
  clear: never; // Disable clear method
}

๐Ÿš€ Advanced Generic Interface Patterns

๐ŸŽจ Interface Composition

Building complex interfaces from simpler ones:

// ๐ŸŽฏ Base interfaces with generics
interface Identifiable<T> {
  id: T;
}

interface Timestamped {
  createdAt: Date;
  updatedAt: Date;
}

interface Versionable {
  version: number;
}

// ๐Ÿ—๏ธ Composed interface
interface Entity<ID> extends Identifiable<ID>, Timestamped, Versionable {
  metadata?: Record<string, any>;
}

// Usage with different ID types
interface User extends Entity<number> {
  username: string;
  email: string;
}

interface Product extends Entity<string> {
  name: string;
  price: number;
  sku: string;
}

// ๐Ÿ”ง Generic repository pattern
interface Repository<T extends Identifiable<any>, ID> {
  findById(id: ID): Promise<T | null>;
  findAll(): Promise<T[]>;
  findWhere(predicate: (item: T) => boolean): Promise<T[]>;
  create(item: Omit<T, 'id'>): Promise<T>;
  update(id: ID, updates: Partial<T>): Promise<T | null>;
  delete(id: ID): Promise<boolean>;
}

// Implementation
class UserRepository implements Repository<User, number> {
  async findById(id: number): Promise<User | null> {
    // Database query implementation
    return null;
  }
  
  async findAll(): Promise<User[]> {
    return [];
  }
  
  async findWhere(predicate: (user: User) => boolean): Promise<User[]> {
    const allUsers = await this.findAll();
    return allUsers.filter(predicate);
  }
  
  async create(userData: Omit<User, 'id'>): Promise<User> {
    const id = Math.floor(Math.random() * 1000);
    const now = new Date();
    return {
      id,
      ...userData,
      createdAt: now,
      updatedAt: now,
      version: 1
    };
  }
  
  async update(id: number, updates: Partial<User>): Promise<User | null> {
    // Update implementation
    return null;
  }
  
  async delete(id: number): Promise<boolean> {
    return true;
  }
}

๐Ÿ”„ Functional Generic Interfaces

Creating type-safe functional patterns:

// ๐ŸŽฏ Generic function interfaces
interface Mapper<T, U> {
  (item: T): U;
}

interface Reducer<T, U> {
  (accumulator: U, current: T, index: number, array: T[]): U;
}

interface Predicate<T> {
  (item: T): boolean;
}

interface Comparator<T> {
  (a: T, b: T): number;
}

// ๐Ÿ—๏ธ Collection with functional methods
interface FunctionalCollection<T> {
  map<U>(mapper: Mapper<T, U>): FunctionalCollection<U>;
  filter(predicate: Predicate<T>): FunctionalCollection<T>;
  reduce<U>(reducer: Reducer<T, U>, initial: U): U;
  sort(comparator?: Comparator<T>): FunctionalCollection<T>;
  find(predicate: Predicate<T>): T | undefined;
  every(predicate: Predicate<T>): boolean;
  some(predicate: Predicate<T>): boolean;
}

// Implementation
class ArrayList<T> implements FunctionalCollection<T> {
  constructor(private items: T[]) {}
  
  map<U>(mapper: Mapper<T, U>): ArrayList<U> {
    return new ArrayList(this.items.map(mapper));
  }
  
  filter(predicate: Predicate<T>): ArrayList<T> {
    return new ArrayList(this.items.filter(predicate));
  }
  
  reduce<U>(reducer: Reducer<T, U>, initial: U): U {
    return this.items.reduce(reducer, initial);
  }
  
  sort(comparator?: Comparator<T>): ArrayList<T> {
    const sorted = [...this.items];
    if (comparator) {
      sorted.sort(comparator);
    }
    return new ArrayList(sorted);
  }
  
  find(predicate: Predicate<T>): T | undefined {
    return this.items.find(predicate);
  }
  
  every(predicate: Predicate<T>): boolean {
    return this.items.every(predicate);
  }
  
  some(predicate: Predicate<T>): boolean {
    return this.items.some(predicate);
  }
}

// ๐Ÿ”ง Advanced functional interface
interface Pipeline<T> {
  value: T;
  pipe<U>(transform: (value: T) => U): Pipeline<U>;
  tap(effect: (value: T) => void): Pipeline<T>;
  unwrap(): T;
}

class PipelineImpl<T> implements Pipeline<T> {
  constructor(public value: T) {}
  
  pipe<U>(transform: (value: T) => U): Pipeline<U> {
    return new PipelineImpl(transform(this.value));
  }
  
  tap(effect: (value: T) => void): Pipeline<T> {
    effect(this.value);
    return this;
  }
  
  unwrap(): T {
    return this.value;
  }
}

// Usage
const result = new PipelineImpl(5)
  .pipe(n => n * 2)
  .pipe(n => n + 1)
  .tap(n => console.log('Current value:', n))
  .pipe(n => n.toString())
  .unwrap(); // "11"

๐ŸŽช Real-World Applications

๐ŸŒ Generic API Interfaces

Building type-safe API contracts:

// ๐ŸŽฏ Generic HTTP response
interface ApiResponse<T> {
  data?: T;
  error?: {
    code: string;
    message: string;
    details?: any;
  };
  metadata: {
    timestamp: Date;
    requestId: string;
    duration: number;
  };
}

// ๐Ÿ—๏ธ Paginated response
interface PaginatedResponse<T> extends ApiResponse<T[]> {
  pagination: {
    page: number;
    pageSize: number;
    totalItems: number;
    totalPages: number;
    hasNext: boolean;
    hasPrevious: boolean;
  };
}

// ๐Ÿ”ง RESTful API interface
interface RestApi<T extends { id: string | number }> {
  getAll(params?: QueryParams): Promise<PaginatedResponse<T>>;
  getById(id: T['id']): Promise<ApiResponse<T>>;
  create(data: Omit<T, 'id'>): Promise<ApiResponse<T>>;
  update(id: T['id'], data: Partial<T>): Promise<ApiResponse<T>>;
  delete(id: T['id']): Promise<ApiResponse<void>>;
}

interface QueryParams {
  page?: number;
  pageSize?: number;
  sort?: string;
  order?: 'asc' | 'desc';
  filter?: Record<string, any>;
}

// Implementation
class UserApiService implements RestApi<User> {
  private baseUrl = '/api/users';
  
  async getAll(params?: QueryParams): Promise<PaginatedResponse<User>> {
    const response = await fetch(`${this.baseUrl}?${this.buildQuery(params)}`);
    return response.json();
  }
  
  async getById(id: number): Promise<ApiResponse<User>> {
    const response = await fetch(`${this.baseUrl}/${id}`);
    return response.json();
  }
  
  async create(userData: Omit<User, 'id'>): Promise<ApiResponse<User>> {
    const response = await fetch(this.baseUrl, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(userData)
    });
    return response.json();
  }
  
  async update(id: number, updates: Partial<User>): Promise<ApiResponse<User>> {
    const response = await fetch(`${this.baseUrl}/${id}`, {
      method: 'PATCH',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(updates)
    });
    return response.json();
  }
  
  async delete(id: number): Promise<ApiResponse<void>> {
    const response = await fetch(`${this.baseUrl}/${id}`, {
      method: 'DELETE'
    });
    return response.json();
  }
  
  private buildQuery(params?: QueryParams): string {
    if (!params) return '';
    const query = new URLSearchParams();
    Object.entries(params).forEach(([key, value]) => {
      if (value !== undefined) {
        query.append(key, String(value));
      }
    });
    return query.toString();
  }
}

๐ŸŽฎ State Management Interfaces

Generic interfaces for state management:

// ๐ŸŽฏ Generic store interface
interface Store<S> {
  getState(): S;
  setState(updater: S | ((prev: S) => S)): void;
  subscribe(listener: (state: S) => void): () => void;
  reset(): void;
}

// ๐Ÿ—๏ธ Slice interface for modular state
interface StateSlice<T, S> {
  name: keyof S;
  initialState: T;
  reducers: {
    [K: string]: (state: T, payload?: any) => T;
  };
  selectors?: {
    [K: string]: (state: T) => any;
  };
}

// ๐Ÿ”ง Action interface
interface Action<T = any> {
  type: string;
  payload?: T;
  meta?: {
    timestamp: Date;
    [key: string]: any;
  };
}

// ๐ŸŽจ Enhanced store with middleware
interface EnhancedStore<S> extends Store<S> {
  dispatch(action: Action): void;
  addMiddleware(middleware: Middleware<S>): void;
  createSlice<K extends keyof S>(slice: StateSlice<S[K], S>): void;
}

interface Middleware<S> {
  (store: Store<S>): (next: (action: Action) => void) => (action: Action) => void;
}

// Implementation
class StoreImpl<S> implements EnhancedStore<S> {
  private state: S;
  private listeners = new Set<(state: S) => void>();
  private middlewares: Middleware<S>[] = [];
  private slices = new Map<keyof S, StateSlice<any, S>>();
  
  constructor(initialState: S) {
    this.state = initialState;
  }
  
  getState(): S {
    return this.state;
  }
  
  setState(updater: S | ((prev: S) => S)): void {
    const newState = typeof updater === 'function'
      ? (updater as (prev: S) => S)(this.state)
      : updater;
    
    this.state = newState;
    this.notifyListeners();
  }
  
  subscribe(listener: (state: S) => void): () => void {
    this.listeners.add(listener);
    return () => this.listeners.delete(listener);
  }
  
  reset(): void {
    // Reset to initial state
    const initialState = {} as S;
    this.slices.forEach((slice, name) => {
      initialState[name] = slice.initialState;
    });
    this.setState(initialState);
  }
  
  dispatch(action: Action): void {
    // Apply middleware chain
    let dispatch = (action: Action) => this.applyAction(action);
    
    for (const middleware of this.middlewares.reverse()) {
      const next = dispatch;
      dispatch = middleware(this)(next);
    }
    
    dispatch(action);
  }
  
  addMiddleware(middleware: Middleware<S>): void {
    this.middlewares.push(middleware);
  }
  
  createSlice<K extends keyof S>(slice: StateSlice<S[K], S>): void {
    this.slices.set(slice.name, slice);
  }
  
  private applyAction(action: Action): void {
    const [sliceName, reducerName] = action.type.split('/');
    const slice = this.slices.get(sliceName as keyof S);
    
    if (slice && slice.reducers[reducerName]) {
      const newSliceState = slice.reducers[reducerName](
        this.state[slice.name],
        action.payload
      );
      
      this.setState({
        ...this.state,
        [slice.name]: newSliceState
      });
    }
  }
  
  private notifyListeners(): void {
    this.listeners.forEach(listener => listener(this.state));
  }
}

// Usage example
interface AppState {
  user: UserState;
  products: ProductState;
}

interface UserState {
  currentUser: User | null;
  isLoading: boolean;
}

interface ProductState {
  items: Product[];
  selectedId: string | null;
}

const store = new StoreImpl<AppState>({
  user: { currentUser: null, isLoading: false },
  products: { items: [], selectedId: null }
});

// Create slices
store.createSlice({
  name: 'user',
  initialState: { currentUser: null, isLoading: false },
  reducers: {
    setUser: (state, user: User) => ({ ...state, currentUser: user }),
    setLoading: (state, loading: boolean) => ({ ...state, isLoading: loading })
  }
});

๐Ÿ”’ Type-Safe Event System

Generic interfaces for event handling:

// ๐ŸŽฏ Event map interface
interface EventMap {
  [event: string]: any[];
}

// ๐Ÿ—๏ธ Generic event emitter
interface EventEmitter<T extends EventMap> {
  on<K extends keyof T>(event: K, handler: (...args: T[K]) => void): void;
  off<K extends keyof T>(event: K, handler: (...args: T[K]) => void): void;
  once<K extends keyof T>(event: K, handler: (...args: T[K]) => void): void;
  emit<K extends keyof T>(event: K, ...args: T[K]): void;
  removeAllListeners(event?: keyof T): void;
}

// ๐Ÿ”ง Observable interface
interface Observable<T> {
  subscribe(observer: Observer<T>): Subscription;
  pipe<U>(...operators: Operator<any, any>[]): Observable<U>;
}

interface Observer<T> {
  next(value: T): void;
  error?(error: any): void;
  complete?(): void;
}

interface Subscription {
  unsubscribe(): void;
  closed: boolean;
}

interface Operator<T, U> {
  (source: Observable<T>): Observable<U>;
}

// ๐ŸŽจ Typed event system implementation
class TypedEventEmitter<T extends EventMap> implements EventEmitter<T> {
  private events = new Map<keyof T, Set<Function>>();
  
  on<K extends keyof T>(event: K, handler: (...args: T[K]) => void): void {
    if (!this.events.has(event)) {
      this.events.set(event, new Set());
    }
    this.events.get(event)!.add(handler);
  }
  
  off<K extends keyof T>(event: K, handler: (...args: T[K]) => void): void {
    this.events.get(event)?.delete(handler);
  }
  
  once<K extends keyof T>(event: K, handler: (...args: T[K]) => void): void {
    const wrappedHandler = (...args: T[K]) => {
      handler(...args);
      this.off(event, wrappedHandler);
    };
    this.on(event, wrappedHandler);
  }
  
  emit<K extends keyof T>(event: K, ...args: T[K]): void {
    this.events.get(event)?.forEach(handler => handler(...args));
  }
  
  removeAllListeners(event?: keyof T): void {
    if (event) {
      this.events.delete(event);
    } else {
      this.events.clear();
    }
  }
}

// Define application events
interface AppEvents {
  login: [user: User];
  logout: [];
  error: [error: Error, context?: string];
  dataUpdate: [type: string, data: any];
  notification: [message: string, level: 'info' | 'warning' | 'error'];
}

const appEvents = new TypedEventEmitter<AppEvents>();

// Type-safe event handling
appEvents.on('login', (user) => {
  console.log(`User ${user.username} logged in`);
});

appEvents.on('error', (error, context) => {
  console.error(`Error in ${context || 'unknown'}: ${error.message}`);
});

appEvents.on('notification', (message, level) => {
  if (level === 'error') {
    console.error(`๐Ÿ”ด ${message}`);
  } else if (level === 'warning') {
    console.warn(`๐ŸŸก ${message}`);
  } else {
    console.log(`๐Ÿ”ต ${message}`);
  }
});

// Emit events with type safety
appEvents.emit('login', { id: 1, username: 'alice', email: '[email protected]' } as User);
appEvents.emit('notification', 'System update completed', 'info');

๐ŸŽฎ Hands-On Exercise

Letโ€™s build a generic caching system with interfaces!

๐Ÿ“ Challenge: Generic Cache Interface System

Create a flexible caching system that:

  1. Supports different cache strategies
  2. Provides type-safe cache operations
  3. Includes TTL (time-to-live) support
  4. Implements cache statistics
// Your challenge: Implement this cache system
interface CacheItem<T> {
  value: T;
  expires?: Date;
  hits: number;
}

interface CacheStrategy<K, V> {
  shouldEvict(key: K, item: CacheItem<V>): boolean;
  onAccess(key: K, item: CacheItem<V>): void;
  onEvict?(key: K, item: CacheItem<V>): void;
}

interface Cache<K, V> {
  get(key: K): V | undefined;
  set(key: K, value: V, ttl?: number): void;
  has(key: K): boolean;
  delete(key: K): boolean;
  clear(): void;
  size(): number;
  stats(): CacheStats;
}

interface CacheStats {
  hits: number;
  misses: number;
  evictions: number;
  size: number;
  hitRate: number;
}

// Example usage to support:
const cache = createCache<string, User>({
  maxSize: 100,
  strategy: 'lru', // or custom strategy
  defaultTTL: 3600000 // 1 hour
});

cache.set('user:123', { id: 123, name: 'Alice' });
const user = cache.get('user:123');
const stats = cache.stats();

๐Ÿ’ก Solution

Click to see the solution
// ๐ŸŽฏ Complete cache system implementation
interface CacheItem<T> {
  value: T;
  expires?: Date;
  hits: number;
  lastAccessed: Date;
  created: Date;
}

interface CacheStrategy<K, V> {
  shouldEvict(key: K, item: CacheItem<V>, cache: Map<K, CacheItem<V>>): boolean;
  onAccess(key: K, item: CacheItem<V>): void;
  onEvict?(key: K, item: CacheItem<V>): void;
  compareForEviction?(a: [K, CacheItem<V>], b: [K, CacheItem<V>]): number;
}

interface Cache<K, V> {
  get(key: K): V | undefined;
  set(key: K, value: V, ttl?: number): void;
  has(key: K): boolean;
  delete(key: K): boolean;
  clear(): void;
  size(): number;
  stats(): CacheStats;
  setStrategy(strategy: CacheStrategy<K, V>): void;
}

interface CacheStats {
  hits: number;
  misses: number;
  evictions: number;
  size: number;
  hitRate: number;
  averageHitsPerItem: number;
}

interface CacheConfig<K, V> {
  maxSize?: number;
  strategy?: 'lru' | 'lfu' | 'fifo' | CacheStrategy<K, V>;
  defaultTTL?: number;
  onEvict?: (key: K, value: V) => void;
}

// ๐Ÿ—๏ธ Cache strategies
class LRUStrategy<K, V> implements CacheStrategy<K, V> {
  shouldEvict(key: K, item: CacheItem<V>, cache: Map<K, CacheItem<V>>): boolean {
    return false; // Eviction handled by compareForEviction
  }
  
  onAccess(key: K, item: CacheItem<V>): void {
    item.lastAccessed = new Date();
  }
  
  compareForEviction(a: [K, CacheItem<V>], b: [K, CacheItem<V>]): number {
    return a[1].lastAccessed.getTime() - b[1].lastAccessed.getTime();
  }
}

class LFUStrategy<K, V> implements CacheStrategy<K, V> {
  shouldEvict(key: K, item: CacheItem<V>, cache: Map<K, CacheItem<V>>): boolean {
    return false;
  }
  
  onAccess(key: K, item: CacheItem<V>): void {
    item.hits++;
    item.lastAccessed = new Date();
  }
  
  compareForEviction(a: [K, CacheItem<V>], b: [K, CacheItem<V>]): number {
    const hitDiff = a[1].hits - b[1].hits;
    if (hitDiff === 0) {
      return a[1].lastAccessed.getTime() - b[1].lastAccessed.getTime();
    }
    return hitDiff;
  }
}

class FIFOStrategy<K, V> implements CacheStrategy<K, V> {
  shouldEvict(key: K, item: CacheItem<V>, cache: Map<K, CacheItem<V>>): boolean {
    return false;
  }
  
  onAccess(key: K, item: CacheItem<V>): void {
    item.lastAccessed = new Date();
  }
  
  compareForEviction(a: [K, CacheItem<V>], b: [K, CacheItem<V>]): number {
    return a[1].created.getTime() - b[1].created.getTime();
  }
}

// ๐Ÿ”ง Main cache implementation
class CacheImpl<K, V> implements Cache<K, V> {
  private cache = new Map<K, CacheItem<V>>();
  private strategy: CacheStrategy<K, V>;
  private stats = {
    hits: 0,
    misses: 0,
    evictions: 0
  };
  
  constructor(private config: CacheConfig<K, V>) {
    this.strategy = this.getStrategy(config.strategy);
  }
  
  get(key: K): V | undefined {
    const item = this.cache.get(key);
    
    if (!item) {
      this.stats.misses++;
      return undefined;
    }
    
    // Check expiration
    if (item.expires && item.expires < new Date()) {
      this.delete(key);
      this.stats.misses++;
      return undefined;
    }
    
    // Update stats and strategy
    this.stats.hits++;
    item.hits++;
    this.strategy.onAccess(key, item);
    
    return item.value;
  }
  
  set(key: K, value: V, ttl?: number): void {
    const effectiveTTL = ttl ?? this.config.defaultTTL;
    const now = new Date();
    
    const item: CacheItem<V> = {
      value,
      expires: effectiveTTL ? new Date(now.getTime() + effectiveTTL) : undefined,
      hits: 0,
      lastAccessed: now,
      created: now
    };
    
    // Check if we need to evict
    if (this.config.maxSize && this.cache.size >= this.config.maxSize && !this.cache.has(key)) {
      this.evictOne();
    }
    
    this.cache.set(key, item);
  }
  
  has(key: K): boolean {
    const item = this.cache.get(key);
    if (!item) return false;
    
    // Check expiration
    if (item.expires && item.expires < new Date()) {
      this.delete(key);
      return false;
    }
    
    return true;
  }
  
  delete(key: K): boolean {
    const item = this.cache.get(key);
    if (item) {
      this.strategy.onEvict?.(key, item);
      this.config.onEvict?.(key, item.value);
    }
    return this.cache.delete(key);
  }
  
  clear(): void {
    this.cache.clear();
    this.stats = { hits: 0, misses: 0, evictions: 0 };
  }
  
  size(): number {
    // Clean up expired items
    this.cleanupExpired();
    return this.cache.size;
  }
  
  stats(): CacheStats {
    const size = this.size();
    const totalAccess = this.stats.hits + this.stats.misses;
    const totalHits = Array.from(this.cache.values())
      .reduce((sum, item) => sum + item.hits, 0);
    
    return {
      hits: this.stats.hits,
      misses: this.stats.misses,
      evictions: this.stats.evictions,
      size,
      hitRate: totalAccess > 0 ? this.stats.hits / totalAccess : 0,
      averageHitsPerItem: size > 0 ? totalHits / size : 0
    };
  }
  
  setStrategy(strategy: CacheStrategy<K, V>): void {
    this.strategy = strategy;
  }
  
  private getStrategy(strategy?: 'lru' | 'lfu' | 'fifo' | CacheStrategy<K, V>): CacheStrategy<K, V> {
    if (!strategy || strategy === 'lru') {
      return new LRUStrategy<K, V>();
    }
    if (strategy === 'lfu') {
      return new LFUStrategy<K, V>();
    }
    if (strategy === 'fifo') {
      return new FIFOStrategy<K, V>();
    }
    return strategy;
  }
  
  private evictOne(): void {
    const entries = Array.from(this.cache.entries());
    
    if (this.strategy.compareForEviction) {
      entries.sort(this.strategy.compareForEviction);
    }
    
    const [keyToEvict] = entries[0];
    if (keyToEvict !== undefined) {
      this.delete(keyToEvict);
      this.stats.evictions++;
    }
  }
  
  private cleanupExpired(): void {
    const now = new Date();
    const keysToDelete: K[] = [];
    
    this.cache.forEach((item, key) => {
      if (item.expires && item.expires < now) {
        keysToDelete.push(key);
      }
    });
    
    keysToDelete.forEach(key => this.delete(key));
  }
}

// ๐ŸŽจ Factory function
function createCache<K, V>(config: CacheConfig<K, V> = {}): Cache<K, V> {
  return new CacheImpl(config);
}

// ๐Ÿ’ซ Advanced cache with metrics
interface MetricsCache<K, V> extends Cache<K, V> {
  getMetrics(): CacheMetrics;
  enableMetrics(enabled: boolean): void;
}

interface CacheMetrics {
  totalRequests: number;
  cacheHitRate: number;
  averageAccessTime: number;
  memoryUsage: number;
  topKeys: Array<{ key: K; hits: number }>;
}

class MetricsCacheImpl<K, V> extends CacheImpl<K, V> implements MetricsCache<K, V> {
  private metricsEnabled = true;
  private accessTimes: number[] = [];
  private keyMetrics = new Map<K, number>();
  
  get(key: K): V | undefined {
    const start = Date.now();
    const result = super.get(key);
    
    if (this.metricsEnabled) {
      this.accessTimes.push(Date.now() - start);
      if (this.accessTimes.length > 1000) {
        this.accessTimes.shift();
      }
      
      this.keyMetrics.set(key, (this.keyMetrics.get(key) || 0) + 1);
    }
    
    return result;
  }
  
  getMetrics(): CacheMetrics {
    const stats = this.stats();
    const topKeys = Array.from(this.keyMetrics.entries())
      .sort((a, b) => b[1] - a[1])
      .slice(0, 10)
      .map(([key, hits]) => ({ key, hits }));
    
    return {
      totalRequests: stats.hits + stats.misses,
      cacheHitRate: stats.hitRate,
      averageAccessTime: this.accessTimes.length > 0
        ? this.accessTimes.reduce((a, b) => a + b, 0) / this.accessTimes.length
        : 0,
      memoryUsage: this.estimateMemoryUsage(),
      topKeys
    };
  }
  
  enableMetrics(enabled: boolean): void {
    this.metricsEnabled = enabled;
    if (!enabled) {
      this.accessTimes = [];
      this.keyMetrics.clear();
    }
  }
  
  private estimateMemoryUsage(): number {
    // Rough estimation
    return this.size() * 100; // bytes per item estimate
  }
}

// ๐Ÿš€ Test the implementation
interface User {
  id: number;
  name: string;
  email: string;
}

async function testCacheSystem() {
  console.log('=== Cache System Test ===\n');
  
  // Create LRU cache
  const userCache = createCache<string, User>({
    maxSize: 3,
    strategy: 'lru',
    defaultTTL: 5000, // 5 seconds
    onEvict: (key, value) => {
      console.log(`Evicted user ${value.name} (${key})`);
    }
  });
  
  // Add users
  userCache.set('user:1', { id: 1, name: 'Alice', email: '[email protected]' });
  userCache.set('user:2', { id: 2, name: 'Bob', email: '[email protected]' });
  userCache.set('user:3', { id: 3, name: 'Charlie', email: '[email protected]' });
  
  // Access pattern
  console.log('User 1:', userCache.get('user:1')?.name);
  console.log('User 2:', userCache.get('user:2')?.name);
  console.log('User 1 again:', userCache.get('user:1')?.name);
  
  // This should evict user:3 (least recently used)
  userCache.set('user:4', { id: 4, name: 'David', email: '[email protected]' });
  
  console.log('\nCache contents:');
  console.log('Has user:1?', userCache.has('user:1'));
  console.log('Has user:2?', userCache.has('user:2'));
  console.log('Has user:3?', userCache.has('user:3')); // Should be false
  console.log('Has user:4?', userCache.has('user:4'));
  
  console.log('\nCache stats:', userCache.stats());
  
  // Test with metrics
  console.log('\n=== Metrics Cache Test ===\n');
  
  const metricsCache = new MetricsCacheImpl<string, User>({
    maxSize: 100,
    strategy: 'lfu'
  });
  
  // Simulate usage
  for (let i = 0; i < 20; i++) {
    metricsCache.set(`user:${i}`, {
      id: i,
      name: `User${i}`,
      email: `user${i}@example.com`
    });
  }
  
  // Access pattern
  for (let i = 0; i < 50; i++) {
    const userId = Math.floor(Math.random() * 25);
    metricsCache.get(`user:${userId}`);
  }
  
  console.log('Metrics:', metricsCache.getMetrics());
  
  // Test TTL
  console.log('\n=== TTL Test ===\n');
  
  const ttlCache = createCache<string, string>({
    defaultTTL: 1000 // 1 second
  });
  
  ttlCache.set('temp', 'This will expire');
  console.log('Immediate get:', ttlCache.get('temp'));
  
  setTimeout(() => {
    console.log('After 1.5 seconds:', ttlCache.get('temp')); // Should be undefined
  }, 1500);
}

testCacheSystem();

๐ŸŽฏ Summary

Youโ€™ve mastered generic interfaces in TypeScript! ๐ŸŽ‰ You learned how to:

  • ๐Ÿ”Œ Create flexible, parameterized interface contracts
  • ๐ŸŽจ Build complex interface hierarchies with composition
  • ๐ŸŒ Design type-safe API definitions
  • ๐Ÿ”„ Implement functional programming patterns
  • ๐ŸŽฎ Create sophisticated state management systems
  • โœจ Maintain complete type safety across generic interfaces

Generic interfaces are fundamental for building professional TypeScript applications. They enable you to create flexible, reusable contracts that adapt to any type while providing excellent developer experience and type safety!

Keep building amazing generic interfaces! ๐Ÿš€