I have lost count of how many times I have re-implemented the distance between two points in a side project. Every map, every drag-and-drop, every collision check, every "snap to nearest" feature ends up needing a small bag of geometry helpers. Here are the five I keep reaching for, with the code I actually paste into utils files, and a few notes on the gotchas that bit me.
1. Distance between two points
The Pythagorean theorem dressed up as a one-liner:
function distance(a, b) {
const dx = a.x - b.x;
const dy = a.y - b.y;
return Math.hypot(dx, dy);
}
Math.hypot is underused. It is the idiomatic way to do sqrt(dx*dx + dy*dy) in JavaScript and it handles overflow and underflow correctly when your numbers are huge or tiny. If you ever wrote Math.sqrt(dx*dx + dy*dy) in production, swap it now.
When I want to double-check my mental model on a specific triangle (especially when working with screen-space sizes), I open the Pythagorean Theorem Calculator on Equation Solver and plug in the two legs. Faster than printing from a REPL.
2. Triangle area from three points
The shoelace formula collapses beautifully for a single triangle:
function triangleArea(a, b, c) {
return Math.abs(
(a.x * (b.y - c.y) +
b.x * (c.y - a.y) +
c.x * (a.y - b.y)) / 2
);
}
No need for base-times-height when you already have coordinates. If you drop the Math.abs, the sign even tells you the orientation (clockwise or counter-clockwise), which is a free win for things like detecting inside/outside.
For a sanity check with side lengths instead of coordinates, the Triangle Calculator on Equation Solver supports both Heron's formula and base/height, which is convenient when you are translating a problem from a textbook.
3. Polygon area for arbitrary closed shapes
The full shoelace formula generalizes the previous one to any simple polygon, given as an array of points in order:
function polygonArea(points) {
let sum = 0;
for (let i = 0; i < points.length; i++) {
const a = points[i];
const b = points[(i + 1) % points.length];
sum += a.x * b.y - b.x * a.y;
}
return Math.abs(sum) / 2;
}
Three gotchas I have hit personally: (1) the polygon must be simple, no self-intersections; (2) the points must be ordered, either all clockwise or all counter-clockwise; (3) the result is in whatever units your coordinates are squared, so mixing pixels and meters silently is painful.
When I am verifying a tricky shape against a reference, I paste the vertices into the Polygon Area Calculator on Equation Solver and compare. It also draws the polygon, which is genuinely helpful when I am trying to spot whether my array is in the wrong winding order.
4. Point inside a circle
Classic, but I still see square roots in code reviews where they are not needed:
function insideCircle(point, center, radius) {
const dx = point.x - center.x;
const dy = point.y - center.y;
return dx * dx + dy * dy <= radius * radius;
}
Never take the square root just to compare against a radius. Squaring both sides is faster, has no precision loss from Math.sqrt, and is exactly what hot-path code in games and physics engines does.
For area and circumference of a given radius, the Circle Calculator on Equation Solver is the page I keep open when I am sizing canvases and viewports.
5. Rectangle overlap
Separating Axis Theorem for the simplest case, axis-aligned rectangles:
function rectsOverlap(a, b) {
return (
a.x < b.x + b.w &&
a.x + a.w > b.x &&
a.y < b.y + b.h &&
a.y + a.h > b.y
);
}
It looks asymmetric, but every comparison there is necessary; drop one and you get false positives or negatives at edges. I have a small unit test that hits the four "just touching" cases, because off-by-one on collision checks shows up as the most embarrassing kind of bug: "the bullet went through the wall."
If you need a refresher on rectangle area and perimeter for sizing layouts, the Rectangle Calculator on Equation Solver is a one-screen reference.
A small style note
I used to put all of these on a Geometry class. I do not anymore. Plain functions composed at the call site are easier to tree-shake, easier to test, and easier to read out of context. Geometry is a great example of "functions over classes" in practice; there is no state to hide.
If there is a sixth one I would add, it is a good clamp(value, min, max) helper. Half of the geometry code I write ends up wanting to clamp something to a range, and writing Math.min(Math.max(...)) for the hundredth time is a sign your toolbox is missing a tool.
Top comments (0)