game performance optimization

Game performance optimization — FPS, memory, GPU profiling, and LOD

📘 gamedev-academy · Part 14 of 15 · Module 5 · Business
A free, step-by-step course on buying, selling & trading domains. View the full series →
Game performance optimization — FPS, memory, GPU profiling, and LOD
Key takeaways
  • Measure first — use an FPS counter and Chrome DevTools to find actual bottlenecks, not guessed ones.
  • Object pooling eliminates garbage collection pauses — the number one cause of frame rate stuttering in browser games.
  • Spatial partitioning (grid or quadtree) reduces collision checks from O(n^2) to near-linear — essential for games with many entities.

Understanding game performance optimization is the foundation of this guide. Below we break it down step by step so you can act with confidence.

Performance is not optional in games. A frame rate drop from 60 to 30 FPS is immediately visible and makes the game feel sluggish. Players will leave a slow game within seconds.

This lesson covers the optimization techniques that keep your game running at a solid 60 FPS across devices — from phones to desktops.

Game Performance Optimization: Measuring performance

You cannot optimize what you do not measure. Use these tools to find bottlenecks:

🔬 Demo: In-game FPS counter and frame budget
class PerformanceMonitor {
  constructor() {
    this.frames = 0;
    this.lastFpsTime = performance.now();
    this.fps = 60;
    this.frameTimes = [];
  }

  frameStart() {
    this._start = performance.now();
  }

  frameEnd() {
    const elapsed = performance.now() - this._start;
    this.frameTimes.push(elapsed);
    if (this.frameTimes.length > 60) this.frameTimes.shift();
    this.frames++;

    const now = performance.now();
    if (now - this.lastFpsTime >= 1000) {
      this.fps = this.frames;
      this.frames = 0;
      this.lastFpsTime = now;
    }
  }

  get avgFrameTime() {
    return this.frameTimes.reduce((a,b) => a+b, 0) / this.frameTimes.length;
  }

  draw(ctx) {
    const color = this.fps >= 55 ? '#4caf50' : this.fps >= 30 ? '#ff9800' : '#f44336';
    ctx.font = '14px monospace';
    ctx.fillStyle = color;
    ctx.fillText(`FPS: ${this.fps} | Frame: ${this.avgFrameTime.toFixed(1)}ms`, 10, 20);
  }
}

// Usage in game loop:
const perf = new PerformanceMonitor();
function loop() {
  perf.frameStart();
  update(); render();
  perf.frameEnd();
  perf.draw(ctx);
  requestAnimationFrame(loop);
}

Target: frame time under 16.6ms. If it consistently exceeds that, you have a bottleneck to find.

Object pooling

Creating and garbage-collecting objects each frame causes GC pauses that stutter your game. Object pooling reuses objects:

🔬 Demo: Object pool for bullets and particles
class ObjectPool {
  constructor(factory, initialSize = 50) {
    this.factory = factory;
    this.pool = [];
    for (let i = 0; i < initialSize; i++)
      this.pool.push(factory());
  }

  acquire() {
    return this.pool.length > 0 ? this.pool.pop() : this.factory();
  }

  release(obj) {
    this.pool.push(obj);
  }
}

// Bullet pool — never allocate during gameplay
const bulletPool = new ObjectPool(() => ({
  x: 0, y: 0, vx: 0, vy: 0, active: false
}), 200);

function fireBullet(x, y, vx, vy) {
  const b = bulletPool.acquire();
  b.x = x; b.y = y; b.vx = vx; b.vy = vy; b.active = true;
  activeBullets.push(b);
}

function removeBullet(b) {
  b.active = false;
  bulletPool.release(b);
}

Spatial partitioning for collision

Checking every entity against every other entity is O(n^2). A spatial hash grid reduces it to near-linear:

🔬 Demo: Spatial hash grid
class SpatialGrid {
  constructor(cellSize) {
    this.cellSize = cellSize;
    this.grid = new Map();
  }

  clear() { this.grid.clear(); }

  _key(x, y) {
    const cx = Math.floor(x / this.cellSize);
    const cy = Math.floor(y / this.cellSize);
    return `${cx},${cy}`;
  }

  insert(entity) {
    const k = this._key(entity.x, entity.y);
    if (!this.grid.has(k)) this.grid.set(k, []);
    this.grid.get(k).push(entity);
  }

  getNearby(entity) {
    const cx = Math.floor(entity.x / this.cellSize);
    const cy = Math.floor(entity.y / this.cellSize);
    const nearby = [];
    for (let dx = -1; dx <= 1; dx++)
      for (let dy = -1; dy <= 1; dy++) {
        const k = `${cx+dx},${cy+dy}`;
        if (this.grid.has(k)) nearby.push(...this.grid.get(k));
      }
    return nearby;
  }
}

// Each frame:
const grid = new SpatialGrid(64);
grid.clear();
entities.forEach(e => grid.insert(e));
entities.forEach(e => {
  for (const other of grid.getNearby(e)) {
    if (other !== e) checkCollision(e, other);
  }
});

Explore Crypto Trading

Cryptocurrency trading is one of the fastest-growing financial markets — open 24/7, globally accessible, and full of opportunity for those who learn the fundamentals first.

  • Trade spot, futures & copy-trading on one platform
  • Start with as little as $10 — practice risk-free on the testnet
  • Built-in TradingView charts, grid bots & DCA tools

Free Crypto Trading Academy → · Domain Trading Academy →

Three.js GPU optimization

For 3D games, GPU performance matters as much as CPU. Key techniques:

🔬 Demo: Three.js performance techniques
// 1. Instanced rendering for many identical objects
const geometry = new THREE.BoxGeometry(0.5, 0.5, 0.5);
const material = new THREE.MeshStandardMaterial({ color: 0x533483 });
const mesh = new THREE.InstancedMesh(geometry, material, 1000);
for (let i = 0; i < 1000; i++) {
  const m = new THREE.Matrix4();
  m.setPosition(Math.random()*50-25, 0.25, Math.random()*50-25);
  mesh.setMatrixAt(i, m);
}
scene.add(mesh); // 1 draw call for 1000 objects

// 2. LOD — swap to simpler models at distance
const lod = new THREE.LOD();
lod.addLevel(highDetailMesh, 0);    // close
lod.addLevel(medDetailMesh, 20);   // medium distance
lod.addLevel(lowDetailMesh, 50);   // far away
scene.add(lod);

// 3. Frustum culling (automatic in Three.js, but help it)
mesh.frustumCulled = true; // default

// 4. Texture atlases — fewer texture swaps = fewer draw calls
// Combine many small textures into one atlas image

// 5. Reduce shadow map updates
sun.shadow.autoUpdate = false;
// Manually update only when needed:
sun.shadow.needsUpdate = true;

When evaluating game performance optimization, start with the fundamentals: what problem it solves, who it serves, and how it compares to the alternatives buyers consider first.

Start with the fundamentals. Define who you are serving, the single outcome you want, and the budget you are comfortable committing before you compare any options.

A practical rule is to favour clarity over cleverness. The choice that is easiest to understand, spell, remember and act on tends to win over the long run.

Context changes the answer. What is optimal for a fast-moving startup is rarely optimal for an established brand, so weigh your own stage, market and ambitions.

Avoid the common mistakes: rushing the decision, copying competitors blindly, and ignoring the total cost of ownership beyond the very first year of use.

Think in terms of trade-offs rather than a single perfect answer. Every option trades memorability, cost, reach and flexibility in a slightly different mix.

Document your reasoning. Writing down why you chose one path makes it far easier to revisit the decision later and to explain it to partners and investors.

Test with real people. A quick check with a handful of your actual customers will surface problems that no amount of internal debate ever reveals.

Plan for growth. The option that fits today should still make sense when your audience, catalogue or team is several times larger than it is right now.

Finally, revisit the decision periodically. Markets, tools and best practices move quickly, and a choice that was ideal last year may deserve a fresh look today.

WSOT 2026: Squad Leader Slots Are Open — Up to $128,000 per Leader

Join Now

Frequently asked questions

What is the biggest performance killer in browser games?

Garbage collection pauses. Creating objects every frame (new bullets, new particles, temporary vectors) triggers GC pauses that stutter the game. Object pooling eliminates this.

How do I profile a browser game?

Chrome DevTools Performance tab: record a few seconds of gameplay, look for long frames (>16ms), identify whether the bottleneck is JavaScript (CPU) or rendering (GPU). The flame chart shows exactly which functions are slow.

Free Courses — Learn New Skills

No sign-up · No fees · Learn at your own pace

Start Trading

Trade crypto and premium domains — digital assets with real opportunities.

Find your name on Atom

DominantBrand curates the best premium, brandable names from Atom.com — the marketplace with a free AI appraisal, a USPTO trademark check, and secure escrow. Every listing even ships with a designed logo.

Browse Domains