DEV Community

Cover image for Memory Management in JavaScript Games — `destroy()`, `hide()`, and Object Pooling
Kehinde Owolabi
Kehinde Owolabi

Posted on

Memory Management in JavaScript Games — `destroy()`, `hide()`, and Object Pooling

Memory Management in JavaScript Games — destroy(), hide(), and Object Pooling

How to Prevent Memory Leaks and Optimize Performance in Limn Engine


📖 Introduction

Memory management is one of the most overlooked aspects of game development. It's easy to focus on features, graphics, and gameplay — but if your game leaks memory, it will eventually slow down, stutter, and crash.

In JavaScript, memory management is "automatic" thanks to garbage collection. But that doesn't mean you can ignore it. In fact, automatic garbage collection can be a trap — if you don't manage your objects properly, you'll create work for the garbage collector, causing frame drops and stuttering.

Limn Engine provides three tools for memory management:

Tool Purpose Use Case
destroy() Permanent removal Bullets, defeated enemies, collected items
hide() / show() Temporary invisibility Respawnable enemies, UI panels
Object Pooling Reuse objects Particles, bullets, frequent spawns

In this guide, we'll cover all three — what they are, when to use them, and why they matter.


🎯 The Problem: What Happens When You Don't Manage Memory?

Scenario 1: The Infinite Bullet

// ❌ WRONG: Bullets never get removed
function shoot() {
    const bullet = new Component(5, 10, "yellow", player.x, player.y, "rect");
    bullet.speedY = -500;
    display.add(bullet);
    bullets.push(bullet);
}

function update(dt) {
    for (let i = 0; i < bullets.length; i++) {
        bullets[i].y += bullets[i].speedY * dt;
        // Bullets keep going forever — even off-screen!
    }
}
Enter fullscreen mode Exit fullscreen mode

The Problem: Every bullet you shoot stays in memory forever. After 10,000 bullets, your game will slow to a crawl.

The Fix: Remove bullets when they leave the screen.

// ✅ CORRECT: Remove off-screen bullets
function update(dt) {
    for (let i = bullets.length - 1; i >= 0; i--) {
        bullets[i].y += bullets[i].speedY * dt;
        if (bullets[i].y < -50) {
            bullets[i].destroy();  // Remove from memory
            bullets.splice(i, 1);   // Remove from array
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Scenario 2: The Enemy That Won't Die

// ❌ WRONG: Enemies just hide — never destroy
function killEnemy(enemy) {
    enemy.hide();  // Disappears, but still in memory!
}

function update() {
    // 100 enemies hidden, still being processed every frame
    for (let i = 0; i < enemies.length; i++) {
        if (enemies[i].health <= 0) {
            enemies[i].hide();  // Still in comm[] !
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

The Problem: Hidden enemies are still in comm[] and still being processed every frame. They just aren't drawn.

The Fix: Use destroy() for permanent removal, hide() only for temporary invisibility.

// ✅ CORRECT: Destroy dead enemies
function killEnemy(enemy) {
    enemy.destroy();  // Remove from comm[] completely
}

// Use hide() only for temporary invisibility
function respawnEnemy(enemy) {
    enemy.hide();           // Temporary
    setTimeout(() => {
        enemy.show();       // Reappear
    }, 3000);
}
Enter fullscreen mode Exit fullscreen mode

🔧 Tool 1: destroy() — Permanent Removal

What It Does: Completely removes a component from the engine's rendering pipeline.

How It Works:

// Inside Component.destroy()
destroy() {
    // Find and remove from comm[] (main display)
    let index = comm.findIndex(c => c.x === this);
    if (index > -1) comm.splice(index, 1);

    // Find and remove from commp[] (fake canvas)
    index = commp.findIndex(c => c.x === this);
    if (index > -1) commp.splice(index, 1);

    // Nullify the update method to prevent accidental calls
    this.update = null;
}
Enter fullscreen mode Exit fullscreen mode

When to Use:

  • Bullets that leave the screen
  • Defeated enemies
  • Collected items
  • Any object that will never be needed again

Example:

// Remove off-screen bullets
if (bullet.y < -50 || bullet.y > 650 || bullet.x < -50 || bullet.x > 850) {
    bullet.destroy();
}

// Remove defeated enemies
if (enemy.health <= 0) {
    enemy.destroy();
}

// Remove collected coins
if (player.crashWith(coin)) {
    coin.destroy();
}
Enter fullscreen mode Exit fullscreen mode

Why It Matters:

  • Prevents comm[] from growing indefinitely
  • Reduces the number of objects processed each frame
  • Prevents memory leaks

🔧 Tool 2: hide() / show() — Temporary Invisibility

What They Do: hide() stops a component from being drawn. show() makes it visible again.

How They Work:

// Inside Component.hide()
hide() {
    this.update = null;  // Stop drawing
}

// Inside Component.show()
show() {
    this.update = this.bUpdate;  // Resume drawing
}
Enter fullscreen mode Exit fullscreen mode

When to Use hide():

  • Enemies that respawn
  • UI panels that toggle
  • Objects that temporarily disappear
  • Anything that will be needed again

Example:

// Enemy respawn system
function respawnEnemy(enemy) {
    // 1. Hide the enemy
    enemy.hide();

    // 2. Wait 3 seconds
    setTimeout(() => {
        // 3. Move to a new position
        enemy.x = Math.random() * 700 + 50;
        enemy.y = Math.random() * 500 + 50;

        // 4. Show the enemy
        enemy.show();
    }, 3000);
}

// UI panel toggle
function toggleMenu() {
    if (menu.visible) {
        menu.hide();
        menu.visible = false;
    } else {
        menu.show();
        menu.visible = true;
    }
}
Enter fullscreen mode Exit fullscreen mode

Why Not Use destroy()?

  • destroy() removes the object from comm[]
  • If you destroy an enemy, you have to recreate it from scratch
  • hide() keeps the object in memory — faster to show again

🔧 Tool 3: Object Pooling — Reuse Objects

What It Is: A design pattern where you pre-create a pool of objects and reuse them instead of creating and destroying them constantly.

Why It Matters:

Approach Performance Memory
Create/Destroy ❌ Slow (GC spikes) ❌ Fragmented
Object Pool ✅ Fast (no GC) ✅ Efficient

How It Works:

  1. Create a pool of objects at startup
  2. When you need an object, take one from the pool
  3. When you're done, return it to the pool
  4. Never create or destroy objects during gameplay

Example: Bullet Pool

class BulletPool {
    constructor(size = 50) {
        this.pool = [];
        this.active = [];

        // Pre-create bullets
        for (let i = 0; i < size; i++) {
            const bullet = new Component(5, 10, "yellow", 0, 0, "rect");
            bullet.hide();  // Start invisible
            this.pool.push(bullet);
        }
    }

    // Get a bullet from the pool
    get(x, y, speedX, speedY) {
        let bullet = this.pool.pop();

        // If pool is empty, create a new one (expands the pool)
        if (!bullet) {
            bullet = new Component(5, 10, "yellow", 0, 0, "rect");
        }

        // Configure the bullet
        bullet.x = x;
        bullet.y = y;
        bullet.speedX = speedX;
        bullet.speedY = speedY;
        bullet.show();
        display.add(bullet);

        this.active.push(bullet);
        return bullet;
    }

    // Return a bullet to the pool
    return(bullet) {
        bullet.hide();
        bullet.x = -1000;  // Move off-screen
        bullet.y = -1000;
        this.pool.push(bullet);

        // Remove from active array
        const index = this.active.indexOf(bullet);
        if (index > -1) this.active.splice(index, 1);
    }

    // Update all active bullets
    update(dt) {
        for (let i = this.active.length - 1; i >= 0; i--) {
            const bullet = this.active[i];
            bullet.y += bullet.speedY * dt;
            bullet.x += bullet.speedX * dt;

            // Return to pool when off-screen
            if (bullet.y < -50 || bullet.y > 650 || 
                bullet.x < -50 || bullet.x > 850) {
                this.return(bullet);
            }
        }
    }
}

// Usage
const bulletPool = new BulletPool(30);

function shoot() {
    bulletPool.get(player.x + 20, player.y, 0, -500);
}

function update(dt) {
    bulletPool.update(dt);
}
Enter fullscreen mode Exit fullscreen mode

📊 Comparison: Which Approach to Use?

Scenario Best Approach Why
Bullets Object Pool High frequency, needs to be fast
Enemies destroy() + recreate Lower frequency, destroy() is fine
Respawnable enemies hide() / show() Reuse the same object
Particles Object Pool High frequency, custom system
UI panels hide() / show() Toggle visibility
Collected items destroy() Never needed again
Tiles destroy() Permanent changes

💡 Pro Tips for Memory Management

1. Always Iterate Backwards When Splicing

// ✅ CORRECT: Reverse iteration
for (let i = enemies.length - 1; i >= 0; i--) {
    if (enemies[i].health <= 0) {
        enemies[i].destroy();
        enemies.splice(i, 1);
    }
}

// ❌ WRONG: Forward iteration skips elements
for (let i = 0; i < enemies.length; i++) {
    if (enemies[i].health <= 0) {
        enemies.splice(i, 1); // Skips the next enemy!
    }
}
Enter fullscreen mode Exit fullscreen mode

2. Use destroy() After Removing from Your Arrays

// 1. Remove from your array
enemies.splice(i, 1);

// 2. Then destroy
enemy.destroy();
Enter fullscreen mode Exit fullscreen mode

3. Pre-Allocate Pools for High-Frequency Objects

// Pre-allocate bullets, particles, and other high-frequency objects
const bulletPool = new BulletPool(100);  // Pre-create 100 bullets
const particlePool = new ParticlePool(500);  // Pre-create 500 particles
Enter fullscreen mode Exit fullscreen mode

4. Profile Your Game

Use the browser's built-in tools:

  • Chrome DevTools → Performance → Record
  • Look for garbage collection spikes
  • If you see regular GC spikes, you need better memory management

🎯 The One-Line Summary

"destroy() for permanent removal, hide() for temporary invisibility, and object pooling for high-frequency objects — use the right tool for the right job."


🔗 Resources

Resource Link
10x Developer Guide limn-engine-doc.vercel.app/10x.html
API Reference limn-engine-doc.vercel.app/reference.html

Draw your game into existence — and clean up after yourself. 🚀

Top comments (0)