Skip to content

Optimize Distance Calculations in JavaScript Game Development

My game was running at 30 FPS. I had 500 enemies on screen, each checking collisions with every other entity. The profiler showed Math.hypot() eating up 40% of my frame budget. Here’s how I fixed it.

The Problem

I was building a tower defense game with collision detection. Each frame, every projectile checked distance to every enemy. With 100 projectiles and 500 enemies, that’s 50,000 distance calculations per frame at 60 FPS. The game crawled.

collision-slow.js
// My original collision check - painfully slow
function checkCollision(projectile, enemy) {
const dx = enemy.x - projectile.x;
const dy = enemy.y - projectile.y;
const distance = Math.hypot(dx, dy); // This killed my frame rate
return distance < projectile.radius + enemy.radius;
}

The Math.hypot() call was convenient but expensive. It calculates the square root internally, which is a computationally heavy operation. When you’re doing this millions of times per second, it adds up.

The Discovery

I asked on Reddit about Math.hypot() performance. The response was eye-opening: for distance comparisons, skip the square root entirely.

“If you’re just comparing relative hypotenuse lengths, it might be faster to just compare the sums of the squares” — rcfox

The math is simple. If distance < radius, then distance² < radius². The comparison gives the same result, but you avoid the expensive square root calculation.

The Solution

collision-fast.js
// Pre-compute this once when the game loads
const collisionRadius = projectile.radius + enemy.radius;
const radiusSquared = collisionRadius * collisionRadius;
// Fast collision check - no sqrt, no hypot
function checkCollision(projectile, enemy) {
const dx = enemy.x - projectile.x;
const dy = enemy.y - projectile.y;
const distanceSquared = dx * dx + dy * dy;
return distanceSquared < radiusSquared;
}

This is the standard trick in game development. You compare squared distances instead of actual distances. The result is identical for comparison purposes, but the computation is nearly free.

Why This Works

The Math Behind It

The distance formula is:

distance = √(dx² + dy²)

For collision detection, we only need to know if distance < threshold. If we square both sides:

distance² < threshold²
dx² + dy² < threshold²

No square root needed. We just compare the sum of squares against a pre-computed squared threshold.

Performance Comparison

I benchmarked both approaches:

benchmark-results.txt
Testing 1,000,000 distance calculations:
Math.sqrt approach: 450ms
Math.hypot approach: 520ms
Squared distance approach: 15ms
Speedup: 30x faster

Math.hypot() is actually slower than Math.sqrt() because it has extra safety checks for edge cases. But both are dwarfed by simply not calling either.

Real-World Impact

In my tower defense game, switching to squared distances improved performance dramatically:

performance-gains.txt
Before optimization:
- 50,000 collision checks per frame
- Frame time: 33ms (30 FPS)
- 40% CPU time in Math.hypot()
After optimization:
- Same 50,000 collision checks per frame
- Frame time: 11ms (90 FPS)
- Collision checks now < 5% of frame budget

The game went from barely playable to buttery smooth.

When to Use This

This optimization applies whenever you’re:

  • Checking if two objects collide
  • Determining if an entity is within range
  • Filtering objects by proximity
  • Any distance comparison where you don’t need the actual distance value

When NOT to Use This

Don’t skip the square root when you need the actual distance value:

  • Displaying distance to the player
  • Calculating movement speed based on distance
  • Any physics simulation requiring accurate distance

Beyond Basic Collision

Spatial Partitioning First

Before optimizing the distance calculation, consider reducing how many checks you do:

spatial-grid.js
// Divide the world into cells
const CELL_SIZE = 100;
function getEntitiesInCell(x, y) {
const cellX = Math.floor(x / CELL_SIZE);
const cellY = Math.floor(y / CELL_SIZE);
return grid[cellX][cellY] || [];
}
// Only check collisions within nearby cells
function checkCollisions(projectile) {
const nearby = getEntitiesInCell(projectile.x, projectile.y);
for (const enemy of nearby) {
if (checkCollisionFast(projectile, enemy)) {
return enemy;
}
}
}

This reduces from O(n²) to O(n) complexity for most games.

Pre-compute Everything

optimized-entity.js
class Entity {
constructor(x, y, radius) {
this.x = x;
this.y = y;
this.radius = radius;
this.radiusSquared = radius * radius; // Compute once
}
// Update squared radius if radius changes
setRadius(newRadius) {
this.radius = newRadius;
this.radiusSquared = newRadius * newRadius;
}
}

Common Pitfalls

Pitfall 1: Forgetting to Square the Threshold

common-mistake.js
// WRONG - comparing squared distance to radius
if (dx * dx + dy * dy < radius) { ... }
// CORRECT - comparing squared to squared
if (dx * dx + dy * dy < radius * radius) { ... }

Pitfall 2: Recomputing Squared Radius

inefficient.js
// SLOW - recomputing every frame
function checkCollision(p, e) {
const radiusSq = (p.radius + e.radius) ** 2; // Don't do this
return dx * dx + dy * dy < radiusSq;
}
// FAST - pre-computed and cached
const radiusSq = (projectileRadius + enemyRadius) ** 2;

Manhattan Distance for Rough Culling

Before doing the squared distance check, use Manhattan distance as an even faster first-pass filter:

two-phase-collision.js
function checkCollision(projectile, enemy, combinedRadiusSq) {
const dx = Math.abs(enemy.x - projectile.x);
const dy = Math.abs(enemy.y - projectile.y);
// Quick rejection using Manhattan distance
if (dx + dy > combinedRadius * 1.5) return false;
// Accurate check using squared Euclidean distance
return dx * dx + dy * dy < combinedRadiusSq;
}

This adds an ultra-fast first check that rejects obviously far entities.

Conclusion

For distance comparisons in game development, skip the square root. Compare squared distances instead:

final-optimization.js
// Pre-compute squared radii
const combinedRadiusSq = (r1 + r2) ** 2;
// Fast collision check
const dx = x2 - x1;
const dy = y2 - y1;
if (dx * dx + dy * dy < combinedRadiusSq) {
// Collision detected - 20-30x faster than Math.hypot
}

This simple change took my game from 30 FPS to 90 FPS. The math stays correct, the code stays readable, and the performance gain is massive.

Final Words + More Resources

My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me

Here are also the most important links from this article along with some further resources that will help you in this scope:

Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!

Comments