StructuralTypeScriptverifiedVerified
Flyweight Pattern in TypeScript
Minimizes memory usage by sharing fine-grained objects that represent repeated data, storing intrinsic state once and passing extrinsic state at call time.
How to Implement the Flyweight Pattern in TypeScript
1Step 1: Define the shared intrinsic state
interface CharacterStyle {
font: string;
size: number;
bold: boolean;
}2Step 2: Create the Flyweight that holds shared state
class Character {
constructor(
public readonly char: string,
public readonly style: CharacterStyle,
) {}
render(x: number, y: number): void {
console.log(
`"${this.char}" [${this.style.font} ${this.style.size}px${this.style.bold ? " bold" : ""}] @ (${x},${y})`,
);
}
}3Step 3: Build a factory that pools and reuses flyweights
class CharacterFactory {
private pool = new Map<string, Character>();
get(char: string, style: CharacterStyle): Character {
const key = `${char}|${style.font}|${style.size}|${style.bold}`;
if (!this.pool.has(key)) {
this.pool.set(key, new Character(char, style));
console.log(`[Factory] created flyweight for key "${key}"`);
}
return this.pool.get(key)!;
}
get poolSize(): number { return this.pool.size; }
}4Step 4: Render text and verify flyweight reuse
const factory = new CharacterFactory();
const style: CharacterStyle = { font: "Arial", size: 12, bold: false };
// Rendering "ABBA" – "A" and "B" flyweights are reused
const text = ["A", "B", "B", "A"];
text.forEach((ch, i) => factory.get(ch, style).render(i * 10, 0));
console.log("Flyweights in pool:", factory.poolSize); // 2, not 4// Flyweight Pattern – Production
// Game entity system: sprites share heavy intrinsic state (texture, mesh, stats)
// while each instance only stores lightweight extrinsic state (position, health).
// ── Intrinsic (shared) state ───────────────────────────────────────────────────
export interface EntityType {
readonly kind: string;
readonly textureUrl: string;
readonly meshData: Float32Array; // large; shared across all instances
readonly baseStats: { attack: number; defense: number; speed: number };
}
// ── Extrinsic (per-instance) state ────────────────────────────────────────────
export interface EntityState {
x: number;
y: number;
health: number;
}
// ── Flyweight ─────────────────────────────────────────────────────────────────
export class GameEntity {
constructor(public readonly type: EntityType) {}
render(state: EntityState): void {
// In a real engine this would issue a GPU draw call using type.meshData
console.log(
`[${this.type.kind}] pos=(${state.x},${state.y}) hp=${state.health} tex=${this.type.textureUrl}`,
);
}
update(state: EntityState, deltaMs: number): EntityState {
// Simplified movement – real logic would use type.baseStats.speed
return { ...state, x: state.x + this.type.baseStats.speed * (deltaMs / 1000) };
}
}
// ── Factory / registry ────────────────────────────────────────────────────────
export class EntityFactory {
private flyweights = new Map<string, GameEntity>();
register(type: EntityType): void {
if (!this.flyweights.has(type.kind)) {
this.flyweights.set(type.kind, new GameEntity(type));
console.log(`[EntityFactory] registered flyweight "${type.kind}"`);
}
}
get(kind: string): GameEntity {
const fw = this.flyweights.get(kind);
if (!fw) throw new Error(`Unknown entity kind: "${kind}"`);
return fw;
}
get flyweightCount(): number { return this.flyweights.size; }
}
// ── Usage ──────────────────────────────────────────────────────────────────────
const factory = new EntityFactory();
factory.register({
kind: "orc",
textureUrl: "/textures/orc.png",
meshData: new Float32Array(1024), // large shared buffer
baseStats: { attack: 15, defense: 8, speed: 3 },
});
factory.register({
kind: "elf",
textureUrl: "/textures/elf.png",
meshData: new Float32Array(1024),
baseStats: { attack: 12, defense: 6, speed: 7 },
});
// Thousands of entities share just 2 flyweights
const entities: Array<{ fw: GameEntity; state: EntityState }> = [];
for (let i = 0; i < 1000; i++) {
const kind = i % 2 === 0 ? "orc" : "elf";
entities.push({ fw: factory.get(kind), state: { x: i * 2, y: 0, health: 100 } });
}
// Render first 3 to verify
entities.slice(0, 3).forEach(({ fw, state }) => fw.render(state));
console.log("Flyweights used:", factory.flyweightCount); // 2, not 1000Flyweight Pattern Architecture
hourglass_empty
Rendering diagram...
lightbulb
Flyweight Pattern in the Real World
“A book publisher doesn’t print a separate metal typeface block for every letter ‘e’ on every page. Instead, one block for ‘e’ (intrinsic state) is reused in every position, with the printer supplying the ink color and position (extrinsic state) each time it is stamped. Thousands of impressions share one piece of metal.”