Skip to content

Can You Skip sqrt for Faster Collision Detection in JavaScript?

The Problem

I was building a canvas game with collision detection. My game loop was struggling—60 FPS was dropping to 30 FPS when I had more than 200 objects on screen. The profiler pointed directly at my distance calculations.

collision-naive.js
function checkCollision(obj1, obj2, radius) {
const dx = obj1.x - obj2.x;
const dy = obj1.y - obj2.y;
const distance = Math.hypot(dx, dy);
return distance < radius;
}

With 200 objects checking collisions against each other, I was doing ~20,000 Math.hypot() calls per frame. At 60 FPS, that’s 1.2 million calls per second. The sqrt operation inside Math.hypot() was killing my performance.

The Discovery

I posted about this on Reddit and got a response that changed everything:

“For distance comparisons (like collision detection), you can skip the sqrt entirely and compare squared distances. No sqrt, no hypot, just multiplication and comparison. This is the standard trick in game dev and it makes a measurable difference in hot loops” — tokagemushi

Another user confirmed:

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

The math is straightforward. For any positive values a and b:

a < b is equivalent to a² < b²

Since I only cared whether distance < radius, not the actual distance value, I could compare squared distances directly and skip the expensive sqrt entirely.

The Solution

I rewrote my collision check:

collision-optimized.js
function checkCollision(obj1, obj2, radius) {
const dx = obj1.x - obj2.x;
const dy = obj1.y - obj2.y;
const distanceSquared = dx * dx + dy * dy;
const radiusSquared = radius * radius;
return distanceSquared < radiusSquared;
}

No Math.hypot(). No Math.sqrt(). Just three multiplications and one comparison.

Why This Works

The Mathematical Reasoning

The distance formula is:

distance = sqrt(dx² + dy²)

For collision detection, I only need to know if distance < radius. Squaring both sides:

distance² < radius²
dx² + dy² < radius²

The comparison result is identical, but I’ve eliminated the sqrt operation entirely.

CPU Cost Comparison

cpu-cycles.txt
Operation CPU Cycles (approximate)
─────────────────────────────────────────────
Multiplication ~3 cycles
Addition ~1 cycle
Comparison ~1 cycle
sqrt/hypot ~10-50 cycles (varies by implementation)

My optimized version uses ~10 cycles total. The naive version with Math.hypot() uses ~50+ cycles. That’s a 5x improvement per collision check.

Benchmark Results

I created a benchmark to measure the actual difference:

benchmark.js
const iterations = 10_000_000;
const obj1 = { x: 100, y: 200 };
const obj2 = { x: 103, y: 204 };
const radius = 10;
// Test 1: Math.hypot
console.time('Math.hypot');
for (let i = 0; i < iterations; i++) {
const dx = obj1.x - obj2.x;
const dy = obj1.y - obj2.y;
if (Math.hypot(dx, dy) < radius) { /* collision */ }
}
console.timeEnd('Math.hypot');
// Test 2: Squared distance (OPTIMIZED)
console.time('Squared distance');
const radiusSquared = radius * radius;
for (let i = 0; i < iterations; i++) {
const dx = obj1.x - obj2.x;
const dy = obj1.y - obj2.y;
if (dx * dx + dy * dy < radiusSquared) { /* collision */ }
}
console.timeEnd('Squared distance');

Results on V8 (Chrome/Node.js):

benchmark-results.txt
Math.hypot: ~3000ms
Squared distance: ~200ms
Speedup: 15x faster

Real-World Application: Particle System

Here’s how I applied this to a particle collision system:

particle-system.js
class ParticleSystem {
constructor(particles) {
this.particles = particles;
this.collisionRadius = 5;
this.collisionRadiusSquared = 25; // Pre-calculate!
}
checkCollisions() {
for (let i = 0; i < this.particles.length; i++) {
for (let j = i + 1; j < this.particles.length; j++) {
const p1 = this.particles[i];
const p2 = this.particles[j];
const dx = p1.x - p2.x;
const dy = p1.y - p2.y;
if (dx * dx + dy * dy < this.collisionRadiusSquared) {
this.resolveCollision(p1, p2);
}
}
}
}
}

The key optimization is pre-computing collisionRadiusSquared once at initialization, not in the hot loop.

Real-World Application: Bullet-to-Enemy Collision

For a game loop running at 60 FPS:

game-engine.js
class GameEngine {
constructor() {
this.bullets = [];
this.enemies = [];
this.hitRadius = 20;
this.hitRadiusSquared = 400; // Pre-calculate
}
update() {
for (const bullet of this.bullets) {
for (const enemy of this.enemies) {
const dx = bullet.x - enemy.x;
const dy = bullet.y - enemy.y;
if (dx * dx + dy * dy < this.hitRadiusSquared) {
this.handleHit(bullet, enemy);
}
}
}
}
}

With 50 bullets and 200 enemies (10,000 checks per frame), this optimization saved ~1.5ms per frame—enough to maintain 60 FPS.

Common Mistakes

Mistake 1: Comparing Mixed Squared/Unsquared Values

mistake-1.js
// WRONG: Comparing squared distance with unsquared radius
if (dx * dx + dy * dy < radius) { } // Bug!
// CORRECT: Compare squared with squared
if (dx * dx + dy * dy < radius * radius) { }

Mistake 2: Recomputing Squared Radius in Hot Loops

mistake-2.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
class Entity {
constructor(radius) {
this.radius = radius;
this.radiusSquared = radius * radius; // Compute once
}
}

Mistake 3: Using Squared Distance When You Need Actual Distance

mistake-3.js
// WRONG: Need actual distance for physics calculations
function bounceOffWall(ball, wall) {
const dx = ball.x - wall.x;
const dy = ball.y - wall.y;
const distanceSquared = dx * dx + dy * dy;
ball.velocity = reflect(distanceSquared); // Wrong dimension!
}
// CORRECT: Use Math.hypot when you need the actual value
function bounceOffWall(ball, wall) {
const dx = ball.x - wall.x;
const dy = ball.y - wall.y;
const distance = Math.hypot(dx, dy);
const normal = { x: dx / distance, y: dy / distance };
ball.velocity = reflect(ball.velocity, normal);
}

Works in 3D Too

The squared distance trick extends to any dimension:

collision-3d.js
function checkCollision3D(obj1, obj2, radiusSquared) {
const dx = obj1.x - obj2.x;
const dy = obj1.y - obj2.y;
const dz = obj1.z - obj2.z;
return dx * dx + dy * dy + dz * dz < radiusSquared;
}

Performance Impact Summary

performance-table.txt
┌─────────────────────────────────┬───────────────────────────────────────┐
│ Metric │ Impact │
├─────────────────────────────────┼───────────────────────────────────────┤
│ Speedup per check │ 10-15x faster than Math.hypot() │
│ CPU cycles saved │ ~40 cycles per collision check │
│ Memory overhead │ None (fewer function calls) │
│ Code complexity │ Slightly more verbose │
│ Applicability │ Distance comparisons only │
└─────────────────────────────────┴───────────────────────────────────────┘

When to Use This Technique

Use squared distance comparison when:

  • Collision detection between objects
  • Range-based entity queries (e.g., “find enemies within 100px”)
  • Proximity triggers in game logic
  • Any “is distance less than X” comparison

Keep using Math.hypot() or Math.sqrt() when:

  • You need to display the distance value
  • Physics calculations require actual distance
  • Movement speed depends on distance
  • You’re debugging and need readable output

Summary

In this post, I explained how to optimize collision detection by comparing squared distances instead of using Math.sqrt() or Math.hypot(). The key insight is that for distance comparisons, you don’t need the actual distance—just something that preserves the ordering. Squared distance does exactly that.

Key takeaways:

  • Skip sqrt when comparing distances—compare dx² + dy² < radius² instead
  • Pre-calculate squared radii at initialization, not in hot loops
  • This technique works in any dimension (2D, 3D, etc.)
  • Keep using Math.hypot() when you need actual distance values
  • The performance gain is 10-15x in hot paths

This is a fundamental game development optimization that transformed my struggling 30 FPS game into a smooth 60 FPS experience. The math is simple, the code is clean, 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