Disposing Three.js Objects Correctly (No Memory Leaks)

Geometry, material, texture and renderer — all leak GPU memory if you don't call dispose() correctly. The complete checklist I use before every simulation cleanup.

Why Three.js Leaks GPU Memory

JavaScript's garbage collector handles CPU memory automatically. But Three.js objects also hold GPU resources — buffer objects, textures, framebuffers — that are managed through the WebGL API. The garbage collector doesn't know about these. When you discard a Mesh by removing the JavaScript reference, the GPU memory stays allocated until you explicitly call dispose().

In a long-running simulation that creates and discards objects regularly, this adds up to hundreds of megabytes of leaked VRAM, eventually causing crashes or severe performance degradation.

Symptom check: Open Chrome DevTools → Performance → Memory. If GPU memory steadily increases while running your simulation (even when nothing is visually changing), you have a leak.

The Complete Checklist

Geometry

geometry.dispose(); // Frees vertex buffers, index buffers

Materials

material.dispose(); // Frees the compiled shader program

Textures

// Manual texture disposal
material.map?.dispose();
material.normalMap?.dispose();
material.roughnessMap?.dispose();
material.envMap?.dispose();
// ... all map properties

// Or use traversal
scene.traverse((obj) => {
  if (obj.material) {
    Object.values(obj.material).forEach(val => {
      if (val?.isTexture) val.dispose();
    });
  }
});

Render Targets

renderTarget.dispose(); // Frees FBO + attached textures

The Full Teardown Pattern

function disposeMesh(mesh) {
  mesh.geometry?.dispose();
  if (Array.isArray(mesh.material)) {
    mesh.material.forEach(disposeMaterial);
  } else {
    disposeMaterial(mesh.material);
  }
}

function disposeMaterial(mat) {
  if (!mat) return;
  mat.dispose();
  // Dispose all map textures
  ['map','normalMap','roughnessMap','metalnessMap',
   'aoMap','emissiveMap','envMap','lightMap',
   'bumpMap','displacementMap','alphaMap']
    .forEach(key => mat[key]?.dispose());
}

// Scene teardown
scene.traverse((obj) => {
  if (obj.isMesh) disposeMesh(obj);
});
renderer.dispose();
renderer.forceContextLoss();

InstancedMesh

InstancedMesh is just a Mesh — dispose its geometry and material the same way. The instanceMatrix and instanceColor buffer attributes are freed when the geometry is disposed.

Pro tip: Call renderer.info.reset() between scenes and log renderer.info.memory to verify geometry and texture counts drop to zero after your teardown. If they don't, you missed a dispose somewhere.