The Pythagorean Theorem in Programming and Games

Distance checks, collisions, movement, nearest-neighbor search and color matching all run on a2 + b2 = c2. Here is how programmers write it, why they often skip the square root, and the numeric traps to avoid.

By the PythagoreanTheorem.net teamUpdated 10 min read

In programming, the Pythagorean theorem is how code measures the straight-line distance between two points: subtract the coordinates to get the legs dx and dy, then compute √dx2 + dy2. Games use it for range checks and collisions, graphics code for circles and lighting, and data science for Euclidean distance between records with many features.

Distance between two points on screen

Every point on a screen has an x coordinate and a y coordinate. The horizontal and vertical differences between two points are perpendicular, so they are the legs of a right triangle and the distance is the hypotenuse. This is the distance formula in code:

// JavaScript
const dx = enemy.x - player.x;
const dy = enemy.y - player.y;
const distance = Math.hypot(dx, dy);   // same value as Math.sqrt(dx * dx + dy * dy)
# Python 3.8+
import math
distance = math.hypot(ex - px, ey - py)
distance = math.dist((px, py), (ex, ey))   # same thing, from two points

For a player at (120, 340) and an enemy at (300, 100), dx = 180 and dy = −240, so the distance is √1802 + (−240)2 = √90,000 = 300 pixels. The minus sign does not matter, because squaring makes every difference positive. That is also why it does not matter that screen y coordinates usually grow downward, or which point you subtract from which. To check a pair of points by hand, use the distance formula calculator.

Range checks without the square root

Games run distance checks thousands of times per frame: is the enemy close enough to attack, is the coin close enough to collect, which tower should fire? Since both sides of d ≤ r are positive, you can square them without changing the answer:

√dx2 + dy2 ≤ r ⇔ dx2 + dy2 ≤ r2
function inRange(a, b, r) {
  const dx = b.x - a.x, dy = b.y - a.y;
  return dx * dx + dy * dy <= r * r;   // no square root needed
}

Square roots are fast on modern hardware, but in a loop over thousands of objects every saved operation adds up, and the squared version is simpler. Game engines expose it directly; Unity, for example, offers sqrMagnitude next to magnitude. The same idea works for sorting: ordering objects by d2 gives exactly the same order as ordering by d, because squaring never changes the order of non-negative numbers.

r = 110 dx dy enemy player Is dx² + dy² ≤ r² ? No: out of range Yes: in range
As the enemy approaches, the code never takes a square root. It compares dx2 + dy2 with r2 = 12,100 and flips to "in range" the moment the sum drops below it.

Common bugCompare squared with squared. Writing dx * dx + dy * dy <= r compares a squared distance with a plain length, so the range silently shrinks. With r = 250, an enemy only 20 px away gives 202 = 400, and 400 ≤ 250 is false: the code reports "out of range" for something right next to the player.

Circle collisions

Two circles overlap when the distance between their centers is no more than the sum of their radii. Collision code for balls, bullets, characters and planets usually starts with exactly this test, again in squared form.

r₁ = 20 r₂ = 25 dx = 35 dy = 20 d ≈ 40.3 d² = 35² + 20² = 1,625 (r₁ + r₂)² = 45² = 2,025 1,625 ≤ 2,025 → collision
Ball A at (50, 80) with radius 20 and ball B at (85, 100) with radius 25, drawn three times larger. The centers are about 40.3 px apart, less than 20 + 25 = 45, so the balls overlap by about 4.7 px.
function circlesOverlap(a, b) {
  const dx = b.x - a.x, dy = b.y - a.y;
  const rSum = a.r + b.r;
  return dx * dx + dy * dy <= rSum * rSum;
}

circlesOverlap({ x: 50, y: 80, r: 20 }, { x: 85, y: 100, r: 25 });   // true

To respond to the collision you do need the real distance, because you push the circles apart along the line between their centers. Dividing dx and dy by d gives a unit vector, a direction of length 1:

const d = Math.hypot(dx, dy);          // 40.31 in the example (check d > 0 first)
const overlap = rSum - d;              // 4.69 px
const nx = dx / d, ny = dy / d;        // unit vector from a toward b
b.x += nx * overlap;                   // move b out until the circles just touch
b.y += ny * overlap;

Collision tests in 3D add a dz2 term, the 3D version of the theorem. Many collision systems also wrap complicated shapes in invisible "bounding spheres" so that this cheap test can rule out most pairs before any expensive check runs.

Diagonal movement: the √2 bug

A classic beginner bug: the player moves 1 unit per frame right and 1 unit per frame up, so holding both keys moves the player along the diagonal of a unit square. That diagonal has length √12 + 12 = √2 ≈ 1.414, the 45-45-90 triangle again, so diagonal movement is about 41% faster than straight movement.

right: 1 up: 1 both keys: √2 ≈ 1.41 normalized: 1 circle of radius 1
Raw input (1, 1) has length √2 and overshoots the circle of allowed speeds. Dividing by that length brings it back to (0.707, 0.707), length 1.

The fix is to normalize the input: divide it by its own length so that every direction has length 1.

let mx = (keys.right ? 1 : 0) - (keys.left ? 1 : 0);
let my = (keys.down ? 1 : 0) - (keys.up ? 1 : 0);
const len = Math.hypot(mx, my);
if (len > 0) { mx /= len; my /= len; }    // (1, -1) becomes (0.707, -0.707)
player.x += mx * speed * dt;
player.y += my * speed * dt;

Math.hypot versus the naive formula

Computers store most decimals as 64-bit floating-point numbers, which can hold values up to about 1.8 × 10308 and down to about 5 × 10−324. Squaring a large number can overflow to infinity, and squaring a tiny one can underflow to zero, even when the final distance is perfectly ordinary. Math.hypot in JavaScript and math.hypot in Python rescale internally to avoid this.

// JavaScript
const x = 1e200;
Math.sqrt(x * x + x * x);   // Infinity   (x * x overflows)
Math.hypot(x, x);           // 1.414213562373095e+200

const s = 1e-200;
Math.sqrt(s * s + s * s);   // 0          (s * s underflows)
Math.hypot(s, s);           // 1.414213562373095e-200
# Python
import math
x = 1e200
math.sqrt(x * x + x * x)    # inf
x ** 2                      # raises OverflowError
math.hypot(x, x)            # 1.414213562373095e+200

For game coordinates measured in pixels or meters, overflow never happens and either form is fine; the plain formula can even be faster in some engines, because Math.hypot does extra work to stay safe. Use hypot for scientific data, values from users or files, or any time the magnitudes could be extreme. Both functions accept more than two arguments for 3D and higher.

Floating-point pitfalls

Floating-point numbers store most decimals only approximately, because they are written in binary. Small rounding errors are usually harmless, but they break exact comparisons.

Checking for a right triangle with decimals

The sides 0.5, 1.2 and 1.3 form a right triangle (the 5-12-13 triangle scaled by 0.1), yet the obvious test fails:

0.5 * 0.5 + 1.2 * 1.2 === 1.3 * 1.3;   // false: 1.69 versus 1.6900000000000002

const close = (p, q, tol = 1e-9) =>
  Math.abs(p - q) <= tol * Math.max(1, Math.abs(p), Math.abs(q));
close(0.5 ** 2 + 1.2 ** 2, 1.3 ** 2);   // true

Python has this built in as math.isclose. Compare with a tolerance whenever the inputs are decimals or measurements. To test a set of side lengths without writing any code, use the right triangle checker.

Whole numbers: exact, up to a limit

JavaScript numbers represent every integer exactly only up to 253 − 1 = 9,007,199,254,740,991. The test a * a + b * b === c * c is reliable while every side is below about 94 million, so that c2 stays under that limit. Beyond it, use BigInt:

const a = 2000001, b = 2000002000000, c = 2000002000001;   // a genuine triple
a * a + b * b === c * c;                                // false: squares too big
BigInt(a) ** 2n + BigInt(b) ** 2n === BigInt(c) ** 2n;  // true

Python integers never overflow, and math.isqrt gives an exact integer square root, which makes it the easy choice for searching for Pythagorean triples (or let the triples generator do it for you):

import math

def is_right_triangle(a, b, c):       # whole-number sides, c the longest
    return a * a + b * b == c * c       # exact for any size of integer

def is_perfect_square(n):
    r = math.isqrt(n)                   # exact, no floating point involved
    return r * r == n

Square roots of tiny negative numbers

Finding a leg means computing √c2 − b2. If b and c are equal in theory but rounded differently, the difference can come out as a tiny negative number, and the square root of a negative number is NaN ("not a number"):

const c = 0.3, b = 0.1 * 3;             // b is actually 0.30000000000000004
Math.sqrt(c * c - b * b);               // NaN, because c*c - b*b is about -2.8e-17
Math.sqrt(Math.max(0, c * c - b * b));  // 0

Euclidean distance in data science

A row of data with n numeric features is a point in n-dimensional space, and the theorem extends to any number of dimensions. The Euclidean distance between points p and q is

d(p, q) = √(p1 − q1)2 + (p2 − q2)2 + ⋯ + (pn − qn)2

The k-nearest neighbors method (k-NN) classifies or recommends by finding the k stored points closest to a new one. Here three friends and you have rated the same three films from 1 to 5:

import math

friends = {"Ana": (4, 2, 4), "Ben": (1, 5, 2), "Cy": (3, 3, 5)}
you = (5, 1, 4)

ranked = sorted(friends, key=lambda name: math.dist(you, friends[name]))
print(ranked)   # ['Ana', 'Cy', 'Ben']

The distances are √1 + 1 + 0 ≈ 1.41 to Ana, √4 + 4 + 1 = 3 to Cy and √16 + 16 + 4 = 6 to Ben, so Ana is your nearest neighbor and her other favorites are the best guesses for you.

Scale your features firstEuclidean distance treats every unit equally. If one feature is income in dollars and another is age in years, a $1,000 difference swamps a 30-year one. Data scientists usually standardize each feature (for example, to mean 0 and standard deviation 1) before measuring distances.

The mathematics behind this, including why "perpendicular" still makes sense in 100 dimensions, is on the page about the theorem in n dimensions.

Color distance

An RGB color is three numbers from 0 to 255, so it is a point in a 3D cube, and the distance between two colors is a 3D Pythagorean distance:

function rgbDistance([r1, g1, b1], [r2, g2, b2]) {
  return Math.hypot(r1 - r2, g1 - g2, b1 - b2);
}
rgbDistance([255, 0, 0], [200, 30, 30]);    // about 69.46
rgbDistance([0, 0, 0], [255, 255, 255]);    // about 441.67, the maximum (255√3)

This is good enough for simple jobs like reducing an image to a palette, but equal RGB distances do not look equally different to human eyes. For perceptual work, colors are first converted to the CIELAB color space, where the original color-difference formula, CIE76 ΔE, is again a plain Euclidean distance; later formulas such as CIEDE2000 add corrections.

More places it hides in code

  • Drawing circles. A pixel (x, y) is inside a circle when (x − cx)2 + (y − cy)2 ≤ r2, the equation of a circle used as a test.
  • Pathfinding. The A* algorithm needs an estimate of the remaining distance to the goal. When units can move in any direction, the straight-line distance is a common choice, because no path can be shorter than it; on grids with only four directions, programmers usually switch to the "Manhattan" distance |dx| + |dy|.
  • Maps and GPS. Map software often approximates short distances with a flat-map version of the theorem and uses spherical formulas over long ones, and GPS receivers model every satellite range as a 3D distance; see the navigation page.
  • Lighting and physics. Normalizing surface directions, measuring light falloff and computing gravity between bodies all start with a vector length, as on the physics page.

Frequently asked questions

How is the Pythagorean theorem used in video games?

Mostly to measure distance: whether an enemy is in range, whether two objects collide, how far a sound should carry, or which target is nearest. Games also use it to normalize movement so that diagonal motion is not faster than straight motion. Game development is one of the twelve careers that use the theorem.

Why do programmers compare squared distances?

Because d ≤ r and d2 ≤ r2 give the same answer for non-negative numbers, and the squared form skips the square root. In loops that run thousands of checks per frame, that keeps code simpler and slightly faster. Remember to square the radius too.

What is the difference between Math.hypot and Math.sqrt(x*x + y*y)?

They compute the same mathematical value, but Math.hypot avoids overflow and underflow when the inputs are extremely large or small, and is usually a little more accurate. The plain formula can be faster and is perfectly safe for everyday screen or world coordinates.

What is Euclidean distance?

It is ordinary straight-line distance, computed with the Pythagorean theorem extended to as many coordinates as you have: square each difference, add them all and take the square root. In two dimensions it is the distance formula; in data science it compares rows of data with many features.

Why does my right-triangle check fail with decimals?

Most decimals like 1.2 and 1.3 cannot be stored exactly in binary, so their squares carry tiny rounding errors and an exact equality test fails. Compare with a tolerance instead, for example math.isclose in Python, or work with whole numbers by scaling the sides up first.

Keep exploring