+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Part 293 of 354

๐Ÿ“˜ Flyweight Pattern: Memory Optimization

Master flyweight pattern: memory optimization in TypeScript with practical examples, best practices, and real-world applications ๐Ÿš€

๐Ÿš€Intermediate
25 min read

Prerequisites

  • Basic understanding of JavaScript ๐Ÿ“
  • TypeScript installation โšก
  • VS Code or preferred IDE ๐Ÿ’ป

What you'll learn

  • Understand the concept fundamentals ๐ŸŽฏ
  • Apply the concept in real projects ๐Ÿ—๏ธ
  • Debug common issues ๐Ÿ›
  • Write type-safe code โœจ

๐ŸŽฏ Introduction

Welcome to this exciting tutorial on the Flyweight Pattern! ๐ŸŽ‰ In this guide, weโ€™ll explore how to optimize memory usage in TypeScript applications by sharing common data efficiently.

Youโ€™ll discover how the Flyweight Pattern can transform your TypeScript development experience when building applications that need to handle thousands of similar objects. Whether youโ€™re creating games ๐ŸŽฎ, rendering graphics ๐ŸŽจ, or managing large datasets ๐Ÿ“Š, understanding the Flyweight Pattern is essential for writing memory-efficient code.

By the end of this tutorial, youโ€™ll feel confident using the Flyweight Pattern to optimize your own projects! Letโ€™s dive in! ๐ŸŠโ€โ™‚๏ธ

๐Ÿ“š Understanding Flyweight Pattern

๐Ÿค” What is the Flyweight Pattern?

The Flyweight Pattern is like a car-sharing service ๐Ÿš—. Think of it as sharing common resources among multiple users instead of everyone owning their own car. Just like how car-sharing reduces the total number of cars needed in a city, the Flyweight Pattern reduces memory usage by sharing common data!

In TypeScript terms, the Flyweight Pattern separates an objectโ€™s intrinsic state (shared data) from its extrinsic state (unique data). This means you can:

  • โœจ Share common data across thousands of objects
  • ๐Ÿš€ Reduce memory footprint dramatically
  • ๐Ÿ›ก๏ธ Maintain object-oriented design principles

๐Ÿ’ก Why Use the Flyweight Pattern?

Hereโ€™s why developers love the Flyweight Pattern:

  1. Memory Efficiency ๐Ÿ”’: Store shared data only once
  2. Performance Boost ๐Ÿ’ป: Less memory allocation means faster operations
  3. Scalability ๐Ÿ“–: Handle millions of objects without memory issues
  4. Clean Architecture ๐Ÿ”ง: Separate concerns between shared and unique data

Real-world example: Imagine building a text editor ๐Ÿ“. With the Flyweight Pattern, you can share font and style information among thousands of characters instead of storing it for each one!

๐Ÿ”ง Basic Syntax and Usage

๐Ÿ“ Simple Example

Letโ€™s start with a friendly example:

// ๐Ÿ‘‹ Hello, Flyweight Pattern!
interface TreeType {
  name: string;     // ๐ŸŒณ Tree species
  color: string;    // ๐ŸŽจ Leaf color
  texture: string;  // ๐Ÿ–ผ๏ธ Bark texture
  
  // ๐ŸŽฏ Draw method uses extrinsic state
  draw(x: number, y: number): void;
}

// ๐Ÿ—๏ธ Concrete flyweight
class ConcreteTreeType implements TreeType {
  constructor(
    public name: string,
    public color: string,
    public texture: string
  ) {}
  
  draw(x: number, y: number): void {
    console.log(`๐ŸŒณ Drawing ${this.name} tree at (${x}, ${y})`);
  }
}

// ๐Ÿญ Flyweight factory
class TreeFactory {
  private static treeTypes: Map<string, TreeType> = new Map();
  
  // โœจ Get or create tree type
  static getTreeType(name: string, color: string, texture: string): TreeType {
    const key = `${name}_${color}_${texture}`;
    
    if (!this.treeTypes.has(key)) {
      console.log(`๐Ÿ†• Creating new tree type: ${name}`);
      this.treeTypes.set(key, new ConcreteTreeType(name, color, texture));
    }
    
    return this.treeTypes.get(key)!;
  }
  
  // ๐Ÿ“Š Show memory savings
  static getTreeTypeCount(): number {
    return this.treeTypes.size;
  }
}

๐Ÿ’ก Explanation: Notice how we use a factory to manage shared tree types! The factory ensures we only create one instance of each unique tree type combination.

๐ŸŽฏ Common Patterns

Here are patterns youโ€™ll use daily:

// ๐ŸŒฒ Individual tree with position (extrinsic state)
class Tree {
  constructor(
    private x: number,      // ๐Ÿ“ X position
    private y: number,      // ๐Ÿ“ Y position
    private type: TreeType  // ๐ŸŒณ Shared tree type
  ) {}
  
  // ๐ŸŽจ Draw this tree
  draw(): void {
    this.type.draw(this.x, this.y);
  }
}

// ๐Ÿž๏ธ Forest manager
class Forest {
  private trees: Tree[] = [];
  
  // ๐ŸŒฑ Plant a tree
  plantTree(x: number, y: number, name: string, color: string, texture: string): void {
    const type = TreeFactory.getTreeType(name, color, texture);
    const tree = new Tree(x, y, type);
    this.trees.push(tree);
  }
  
  // ๐ŸŽจ Draw all trees
  draw(): void {
    console.log(`๐Ÿž๏ธ Drawing forest with ${this.trees.length} trees...`);
    this.trees.forEach(tree => tree.draw());
  }
  
  // ๐Ÿ“Š Show memory savings
  showStats(): void {
    console.log(`๐Ÿ“Š Forest Stats:`);
    console.log(`  ๐ŸŒณ Total trees: ${this.trees.length}`);
    console.log(`  ๐Ÿ’พ Unique tree types: ${TreeFactory.getTreeTypeCount()}`);
    console.log(`  ๐Ÿš€ Memory saved: ${((1 - TreeFactory.getTreeTypeCount() / this.trees.length) * 100).toFixed(1)}%`);
  }
}

๐Ÿ’ก Practical Examples

๐Ÿ›’ Example 1: Text Editor Characters

Letโ€™s build something real:

// ๐Ÿ“ Character formatting (intrinsic state)
interface CharacterFormat {
  font: string;      // ๐Ÿ”ค Font family
  size: number;      // ๐Ÿ“ Font size
  color: string;     // ๐ŸŽจ Text color
  bold: boolean;     // ๐Ÿ…ฑ๏ธ Bold style
  italic: boolean;   // ๐Ÿ…ธ๏ธ Italic style
}

// โœจ Flyweight character format
class SharedCharacterFormat implements CharacterFormat {
  constructor(
    public font: string,
    public size: number,
    public color: string,
    public bold: boolean,
    public italic: boolean
  ) {}
  
  // ๐Ÿ”‘ Create unique key
  getKey(): string {
    return `${this.font}_${this.size}_${this.color}_${this.bold}_${this.italic}`;
  }
}

// ๐Ÿญ Format factory
class FormatFactory {
  private static formats: Map<string, SharedCharacterFormat> = new Map();
  
  // ๐ŸŽจ Get or create format
  static getFormat(
    font: string,
    size: number,
    color: string,
    bold: boolean,
    italic: boolean
  ): SharedCharacterFormat {
    const format = new SharedCharacterFormat(font, size, color, bold, italic);
    const key = format.getKey();
    
    if (!this.formats.has(key)) {
      console.log(`๐Ÿ†• Creating new format: ${key}`);
      this.formats.set(key, format);
    }
    
    return this.formats.get(key)!;
  }
  
  // ๐Ÿ“Š Get statistics
  static getFormatCount(): number {
    return this.formats.size;
  }
}

// ๐Ÿ“„ Individual character
class Character {
  constructor(
    private char: string,           // ๐Ÿ”ค The actual character
    private position: number,       // ๐Ÿ“ Position in document
    private format: SharedCharacterFormat  // ๐ŸŽจ Shared formatting
  ) {}
  
  // ๐Ÿ–จ๏ธ Display character
  display(): string {
    const style = this.format.bold ? '**' : '';
    const italic = this.format.italic ? '_' : '';
    return `${style}${italic}${this.char}${italic}${style}`;
  }
}

// ๐Ÿ“ Text editor
class TextEditor {
  private characters: Character[] = [];
  
  // โœ๏ธ Add text with formatting
  addText(text: string, font: string, size: number, color: string, bold: boolean, italic: boolean): void {
    const format = FormatFactory.getFormat(font, size, color, bold, italic);
    
    for (let i = 0; i < text.length; i++) {
      const char = new Character(text[i], this.characters.length, format);
      this.characters.push(char);
    }
    
    console.log(`โœ๏ธ Added "${text}" with format`);
  }
  
  // ๐Ÿ“‹ Display document
  display(): void {
    console.log("๐Ÿ“„ Document content:");
    const content = this.characters.map(char => char.display()).join('');
    console.log(content);
  }
  
  // ๐Ÿ“Š Show memory efficiency
  showStats(): void {
    console.log(`๐Ÿ“Š Editor Stats:`);
    console.log(`  ๐Ÿ”ค Total characters: ${this.characters.length}`);
    console.log(`  ๐ŸŽจ Unique formats: ${FormatFactory.getFormatCount()}`);
    console.log(`  ๐Ÿ’พ Memory efficiency: ${((1 - FormatFactory.getFormatCount() / this.characters.length) * 100).toFixed(1)}%`);
  }
}

// ๐ŸŽฎ Let's use it!
const editor = new TextEditor();
editor.addText("Hello ", "Arial", 12, "black", false, false);
editor.addText("TypeScript", "Arial", 12, "blue", true, false);
editor.addText("! ๐Ÿš€", "Arial", 12, "black", false, false);
editor.display();
editor.showStats();

๐ŸŽฏ Try it yourself: Add a method to change formatting for a range of characters!

๐ŸŽฎ Example 2: Game Particle System

Letโ€™s make it fun:

// ๐ŸŽ† Particle type (intrinsic state)
interface ParticleType {
  texture: string;    // ๐Ÿ–ผ๏ธ Particle texture
  color: string;      // ๐ŸŽจ Base color
  size: number;       // ๐Ÿ“ Base size
  behavior: string;   // ๐ŸŽญ Movement pattern
}

// โœจ Concrete particle type
class ConcreteParticleType implements ParticleType {
  constructor(
    public texture: string,
    public color: string,
    public size: number,
    public behavior: string
  ) {}
  
  // ๐ŸŽฏ Create unique identifier
  getId(): string {
    return `${this.texture}_${this.color}_${this.size}_${this.behavior}`;
  }
}

// ๐Ÿญ Particle factory
class ParticleFactory {
  private static types: Map<string, ParticleType> = new Map();
  
  // ๐ŸŽ† Get or create particle type
  static getParticleType(
    texture: string,
    color: string,
    size: number,
    behavior: string
  ): ParticleType {
    const type = new ConcreteParticleType(texture, color, size, behavior);
    const id = type.getId();
    
    if (!this.types.has(id)) {
      console.log(`๐Ÿ†• Creating particle type: ${texture}`);
      this.types.set(id, type);
    }
    
    return this.types.get(id)!;
  }
  
  // ๐Ÿ“Š Get type count
  static getTypeCount(): number {
    return this.types.size;
  }
}

// ๐ŸŽ‡ Individual particle (extrinsic state)
class Particle {
  constructor(
    public x: number,              // ๐Ÿ“ X position
    public y: number,              // ๐Ÿ“ Y position
    public velocity: number,       // ๐Ÿš€ Speed
    public direction: number,      // ๐Ÿงญ Direction in degrees
    public lifespan: number,       // โฑ๏ธ Remaining life
    private type: ParticleType     // ๐ŸŽ† Shared type
  ) {}
  
  // ๐Ÿ”„ Update particle
  update(deltaTime: number): void {
    const radians = this.direction * Math.PI / 180;
    this.x += Math.cos(radians) * this.velocity * deltaTime;
    this.y += Math.sin(radians) * this.velocity * deltaTime;
    this.lifespan -= deltaTime;
  }
  
  // ๐ŸŽจ Render particle
  render(): void {
    if (this.lifespan > 0) {
      console.log(`โœจ Particle at (${this.x.toFixed(1)}, ${this.y.toFixed(1)})`);
    }
  }
  
  // ๐Ÿ’€ Check if dead
  isDead(): boolean {
    return this.lifespan <= 0;
  }
}

// ๐ŸŽฎ Particle system manager
class ParticleSystem {
  private particles: Particle[] = [];
  private maxParticles: number = 10000;
  
  // ๐ŸŽ† Emit particles
  emit(
    count: number,
    x: number,
    y: number,
    texture: string,
    color: string,
    size: number,
    behavior: string
  ): void {
    const type = ParticleFactory.getParticleType(texture, color, size, behavior);
    
    for (let i = 0; i < count; i++) {
      if (this.particles.length >= this.maxParticles) break;
      
      const particle = new Particle(
        x,
        y,
        Math.random() * 100 + 50,      // ๐ŸŽฒ Random velocity
        Math.random() * 360,           // ๐ŸŽฒ Random direction
        Math.random() * 2 + 1,         // ๐ŸŽฒ Random lifespan
        type
      );
      
      this.particles.push(particle);
    }
    
    console.log(`๐ŸŽ† Emitted ${count} particles!`);
  }
  
  // ๐Ÿ”„ Update all particles
  update(deltaTime: number): void {
    this.particles = this.particles.filter(particle => {
      particle.update(deltaTime);
      return !particle.isDead();
    });
  }
  
  // ๐Ÿ“Š Show system stats
  showStats(): void {
    console.log(`๐ŸŽฎ Particle System Stats:`);
    console.log(`  โœจ Active particles: ${this.particles.length}`);
    console.log(`  ๐ŸŽจ Unique particle types: ${ParticleFactory.getTypeCount()}`);
    console.log(`  ๐Ÿ’พ Memory optimization: ${((1 - ParticleFactory.getTypeCount() / this.particles.length) * 100).toFixed(1)}%`);
  }
}

๐Ÿš€ Advanced Concepts

๐Ÿง™โ€โ™‚๏ธ Advanced Topic 1: Composite Flyweights

When youโ€™re ready to level up, try this advanced pattern:

// ๐ŸŽฏ Complex flyweight with nested shared data
interface ComplexFlyweight {
  primaryData: SharedData;      // ๐ŸŽจ Primary shared data
  secondaryData: SharedData;    // ๐ŸŽญ Secondary shared data
  render(context: RenderContext): void;
}

// ๐Ÿ—๏ธ Shared data structure
class SharedData {
  constructor(
    public id: string,
    public properties: Map<string, any>
  ) {}
}

// ๐ŸŽช Render context (extrinsic)
interface RenderContext {
  x: number;
  y: number;
  scale: number;
  rotation: number;
  opacity: number;
}

// ๐Ÿญ Advanced factory with caching
class AdvancedFlyweightFactory {
  private static cache: Map<string, ComplexFlyweight> = new Map();
  private static sharedDataCache: Map<string, SharedData> = new Map();
  
  // ๐Ÿช„ Create complex flyweight
  static createComplexFlyweight(
    primaryId: string,
    secondaryId: string,
    primaryProps: Map<string, any>,
    secondaryProps: Map<string, any>
  ): ComplexFlyweight {
    const key = `${primaryId}_${secondaryId}`;
    
    if (!this.cache.has(key)) {
      const primary = this.getOrCreateSharedData(primaryId, primaryProps);
      const secondary = this.getOrCreateSharedData(secondaryId, secondaryProps);
      
      const flyweight: ComplexFlyweight = {
        primaryData: primary,
        secondaryData: secondary,
        render(context: RenderContext): void {
          console.log(`โœจ Rendering complex object at (${context.x}, ${context.y})`);
        }
      };
      
      this.cache.set(key, flyweight);
    }
    
    return this.cache.get(key)!;
  }
  
  // ๐Ÿ’พ Get or create shared data
  private static getOrCreateSharedData(id: string, properties: Map<string, any>): SharedData {
    if (!this.sharedDataCache.has(id)) {
      this.sharedDataCache.set(id, new SharedData(id, properties));
    }
    return this.sharedDataCache.get(id)!;
  }
}

๐Ÿ—๏ธ Advanced Topic 2: Thread-Safe Flyweight

For the brave developers:

// ๐Ÿš€ Thread-safe flyweight pool
class ThreadSafeFlyweightPool<T> {
  private pool: T[] = [];
  private inUse: Set<T> = new Set();
  private factory: () => T;
  private maxSize: number;
  
  constructor(factory: () => T, maxSize: number = 100) {
    this.factory = factory;
    this.maxSize = maxSize;
  }
  
  // ๐ŸŽฏ Acquire flyweight from pool
  acquire(): T | null {
    let flyweight: T | undefined;
    
    // ๐Ÿ” Try to find available flyweight
    for (const item of this.pool) {
      if (!this.inUse.has(item)) {
        flyweight = item;
        break;
      }
    }
    
    // ๐Ÿ†• Create new if needed and pool not full
    if (!flyweight && this.pool.length < this.maxSize) {
      flyweight = this.factory();
      this.pool.push(flyweight);
    }
    
    // ๐Ÿ“Œ Mark as in use
    if (flyweight) {
      this.inUse.add(flyweight);
      console.log(`โœ… Acquired flyweight (${this.inUse.size}/${this.pool.length} in use)`);
    }
    
    return flyweight || null;
  }
  
  // ๐Ÿ”„ Release flyweight back to pool
  release(flyweight: T): void {
    if (this.inUse.has(flyweight)) {
      this.inUse.delete(flyweight);
      console.log(`โ™ป๏ธ Released flyweight (${this.inUse.size}/${this.pool.length} in use)`);
    }
  }
  
  // ๐Ÿ“Š Get pool statistics
  getStats(): { total: number; inUse: number; available: number } {
    return {
      total: this.pool.length,
      inUse: this.inUse.size,
      available: this.pool.length - this.inUse.size
    };
  }
}

โš ๏ธ Common Pitfalls and Solutions

๐Ÿ˜ฑ Pitfall 1: Modifying Shared State

// โŒ Wrong way - modifying shared state!
class BadFlyweight {
  public sharedData: string[] = [];
  
  addData(item: string): void {
    this.sharedData.push(item); // ๐Ÿ’ฅ This affects all instances!
  }
}

// โœ… Correct way - keep shared state immutable!
class GoodFlyweight {
  constructor(
    private readonly sharedData: ReadonlyArray<string>
  ) {}
  
  // ๐Ÿ›ก๏ธ Return new array instead of modifying
  addData(item: string): ReadonlyArray<string> {
    return [...this.sharedData, item];
  }
}

๐Ÿคฏ Pitfall 2: Not Identifying Intrinsic vs Extrinsic

// โŒ Dangerous - everything is intrinsic!
class BadCharacter {
  constructor(
    public font: string,
    public size: number,
    public color: string,
    public x: number,      // ๐Ÿ’ฅ Position should be extrinsic!
    public y: number       // ๐Ÿ’ฅ Position should be extrinsic!
  ) {}
}

// โœ… Safe - proper separation!
class CharacterType {
  constructor(
    public readonly font: string,
    public readonly size: number,
    public readonly color: string
  ) {}
}

class GoodCharacter {
  constructor(
    private x: number,              // โœ… Extrinsic
    private y: number,              // โœ… Extrinsic
    private type: CharacterType     // โœ… Intrinsic (shared)
  ) {}
}

๐Ÿ› ๏ธ Best Practices

  1. ๐ŸŽฏ Identify Shared State: Carefully analyze what data can be shared
  2. ๐Ÿ“ Keep Flyweights Immutable: Never modify shared state
  3. ๐Ÿ›ก๏ธ Use Factory Pattern: Centralize flyweight creation and management
  4. ๐ŸŽจ Separate Concerns: Clearly distinguish intrinsic from extrinsic state
  5. โœจ Monitor Memory Usage: Track your memory savings with statistics

๐Ÿงช Hands-On Exercise

๐ŸŽฏ Challenge: Build a Chess Game Renderer

Create a memory-efficient chess game renderer:

๐Ÿ“‹ Requirements:

  • โœ… Chess pieces with shared textures and models
  • ๐Ÿท๏ธ Different piece types (pawn, rook, knight, etc.)
  • ๐Ÿ‘ค Two colors (black and white)
  • ๐Ÿ“… Board positions as extrinsic state
  • ๐ŸŽจ Each piece type needs a unique emoji!

๐Ÿš€ Bonus Points:

  • Add move validation
  • Implement piece animation states
  • Create a move history tracker

๐Ÿ’ก Solution

๐Ÿ” Click to see solution
// ๐ŸŽฏ Our memory-efficient chess system!
interface ChessPieceType {
  name: string;
  emoji: string;
  color: "white" | "black";
  movePattern: string;
}

// โ™Ÿ๏ธ Concrete piece type (flyweight)
class ConcreteChessPieceType implements ChessPieceType {
  constructor(
    public name: string,
    public emoji: string,
    public color: "white" | "black",
    public movePattern: string
  ) {}
  
  getKey(): string {
    return `${this.name}_${this.color}`;
  }
}

// ๐Ÿญ Chess piece factory
class ChessPieceFactory {
  private static pieces: Map<string, ChessPieceType> = new Map();
  
  static getPiece(name: string, color: "white" | "black"): ChessPieceType {
    const emoji = this.getEmoji(name, color);
    const piece = new ConcreteChessPieceType(name, emoji, color, this.getMovePattern(name));
    const key = piece.getKey();
    
    if (!this.pieces.has(key)) {
      console.log(`๐Ÿ†• Creating ${color} ${name}`);
      this.pieces.set(key, piece);
    }
    
    return this.pieces.get(key)!;
  }
  
  private static getEmoji(name: string, color: "white" | "black"): string {
    const emojis: Record<string, Record<"white" | "black", string>> = {
      king: { white: "โ™”", black: "โ™š" },
      queen: { white: "โ™•", black: "โ™›" },
      rook: { white: "โ™–", black: "โ™œ" },
      bishop: { white: "โ™—", black: "โ™" },
      knight: { white: "โ™˜", black: "โ™ž" },
      pawn: { white: "โ™™", black: "โ™Ÿ" }
    };
    return emojis[name][color];
  }
  
  private static getMovePattern(name: string): string {
    const patterns: Record<string, string> = {
      king: "one square any direction",
      queen: "any direction any distance",
      rook: "horizontal/vertical any distance",
      bishop: "diagonal any distance",
      knight: "L-shape",
      pawn: "forward one, capture diagonal"
    };
    return patterns[name];
  }
  
  static getPieceCount(): number {
    return this.pieces.size;
  }
}

// ๐Ÿ Chess piece instance
class ChessPiece {
  constructor(
    private row: number,
    private col: number,
    private type: ChessPieceType
  ) {}
  
  move(newRow: number, newCol: number): void {
    console.log(`${this.type.emoji} Moving ${this.type.name} from (${this.row},${this.col}) to (${newRow},${newCol})`);
    this.row = newRow;
    this.col = newCol;
  }
  
  getPosition(): [number, number] {
    return [this.row, this.col];
  }
  
  getType(): ChessPieceType {
    return this.type;
  }
}

// โ™Ÿ๏ธ Chess board
class ChessBoard {
  private board: (ChessPiece | null)[][] = [];
  private pieces: ChessPiece[] = [];
  
  constructor() {
    // ๐Ÿ Initialize empty board
    for (let i = 0; i < 8; i++) {
      this.board[i] = new Array(8).fill(null);
    }
    this.setupBoard();
  }
  
  private setupBoard(): void {
    // ๐Ÿ‘‘ Setup back row pieces
    const backRowPieces = ["rook", "knight", "bishop", "queen", "king", "bishop", "knight", "rook"];
    
    // โšช White pieces
    backRowPieces.forEach((pieceName, col) => {
      this.addPiece(0, col, pieceName, "white");
    });
    
    // โ™Ÿ๏ธ White pawns
    for (let col = 0; col < 8; col++) {
      this.addPiece(1, col, "pawn", "white");
    }
    
    // โšซ Black pieces
    backRowPieces.forEach((pieceName, col) => {
      this.addPiece(7, col, pieceName, "black");
    });
    
    // โ™Ÿ๏ธ Black pawns
    for (let col = 0; col < 8; col++) {
      this.addPiece(6, col, "pawn", "black");
    }
  }
  
  private addPiece(row: number, col: number, name: string, color: "white" | "black"): void {
    const pieceType = ChessPieceFactory.getPiece(name, color);
    const piece = new ChessPiece(row, col, pieceType);
    this.board[row][col] = piece;
    this.pieces.push(piece);
  }
  
  display(): void {
    console.log("\nโ™Ÿ๏ธ Chess Board:");
    console.log("  a b c d e f g h");
    for (let row = 7; row >= 0; row--) {
      let rowStr = `${row + 1} `;
      for (let col = 0; col < 8; col++) {
        const piece = this.board[row][col];
        rowStr += piece ? piece.getType().emoji + " " : "ยท ";
      }
      console.log(rowStr);
    }
  }
  
  showStats(): void {
    console.log("\n๐Ÿ“Š Chess Board Stats:");
    console.log(`  โ™Ÿ๏ธ Total pieces: ${this.pieces.length}`);
    console.log(`  ๐Ÿ’พ Unique piece types: ${ChessPieceFactory.getPieceCount()}`);
    console.log(`  ๐Ÿš€ Memory efficiency: ${((1 - ChessPieceFactory.getPieceCount() / this.pieces.length) * 100).toFixed(1)}%`);
  }
}

// ๐ŸŽฎ Test it out!
const chessGame = new ChessBoard();
chessGame.display();
chessGame.showStats();

๐ŸŽ“ Key Takeaways

Youโ€™ve learned so much! Hereโ€™s what you can now do:

  • โœ… Create memory-efficient applications with confidence ๐Ÿ’ช
  • โœ… Avoid common flyweight mistakes that trip up beginners ๐Ÿ›ก๏ธ
  • โœ… Apply the pattern in real projects ๐ŸŽฏ
  • โœ… Debug memory issues like a pro ๐Ÿ›
  • โœ… Build scalable systems with TypeScript! ๐Ÿš€

Remember: The Flyweight Pattern is your friend when dealing with thousands of similar objects! Itโ€™s here to help you write memory-efficient code. ๐Ÿค

๐Ÿค Next Steps

Congratulations! ๐ŸŽ‰ Youโ€™ve mastered the Flyweight Pattern!

Hereโ€™s what to do next:

  1. ๐Ÿ’ป Practice with the exercises above
  2. ๐Ÿ—๏ธ Build a particle system or text editor using the Flyweight Pattern
  3. ๐Ÿ“š Move on to our next tutorial: Proxy Pattern: Controlled Access
  4. ๐ŸŒŸ Share your memory optimization success stories!

Remember: Every TypeScript expert was once a beginner. Keep coding, keep learning, and most importantly, have fun! ๐Ÿš€


Happy coding! ๐ŸŽ‰๐Ÿš€โœจ