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.
// My original collision check - painfully slowfunction 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
// Pre-compute this once when the game loadsconst collisionRadius = projectile.radius + enemy.radius;const radiusSquared = collisionRadius * collisionRadius;
// Fast collision check - no sqrt, no hypotfunction 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:
Testing 1,000,000 distance calculations:
Math.sqrt approach: 450msMath.hypot approach: 520msSquared distance approach: 15ms
Speedup: 30x fasterMath.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:
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 budgetThe 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:
// Divide the world into cellsconst 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 cellsfunction 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
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
// WRONG - comparing squared distance to radiusif (dx * dx + dy * dy < radius) { ... }
// CORRECT - comparing squared to squaredif (dx * dx + dy * dy < radius * radius) { ... }Pitfall 2: Recomputing Squared Radius
// SLOW - recomputing every framefunction checkCollision(p, e) { const radiusSq = (p.radius + e.radius) ** 2; // Don't do this return dx * dx + dy * dy < radiusSq;}
// FAST - pre-computed and cachedconst radiusSq = (projectileRadius + enemyRadius) ** 2;Related Techniques
Manhattan Distance for Rough Culling
Before doing the squared distance check, use Manhattan distance as an even faster first-pass filter:
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:
// Pre-compute squared radiiconst combinedRadiusSq = (r1 + r2) ** 2;
// Fast collision checkconst 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