Introduction

Give a computer the corners of a polygon, in order, and ask for its area. The obvious plan is to triangulate — split the shape into triangles, find each triangle's area, add them up. It works, but it is fiddly: you need to choose a triangulation, handle concave corners, and keep signs straight.

There is a shortcut. Walk around the vertices (x1,y1),(x2,y2),…,(xn,yn)(x_1, y_1), (x_2, y_2), \dots, (x_n, y_n) in order, multiply each xx by the next yy, subtract the reverse product, and add it all up. Divide by two and you have the exact area — no triangulation, no case analysis, just arithmetic on the coordinates you already have.

It is called the shoelace formula because the crisscross pattern of multiplications, written out on paper, looks like laces threading through a shoe's eyelets. That visual mnemonic hides a genuinely elegant piece of mathematics: a sum of cross products that also tells you, for free, which way the polygon turns.

Try It

Drag any vertex of the polygon below. The area and the arrow showing which way it winds — clockwise or counter-clockwise — update instantly, computed straight from the coordinates with the shoelace formula.

<p class="hint">Drag any vertex of the polygon. The shoelace formula recomputes the exact <b>area</b> and <b>winding direction</b> from the raw coordinates alone, every time you move a point.</p>
<svg id="canvas" class="canvas" viewBox="0 0 320 240" xmlns="http://www.w3.org/2000/svg"></svg>
<div class="readout" id="readout">Drag a vertex to begin.</div>
<div class="btns">
  <button id="reverse" type="button">Reverse vertex order</button>
  <button id="shuffle" type="button">Shuffle vertices</button>
  <button id="reset" type="button" class="ghost">Reset</button>
</div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.hint { font-size: .9rem; color: #444; margin: 0 0 .6rem; line-height: 1.45; }
.canvas { width: 100%; max-width: 420px; height: 240px; background: #f4f6f8; border: 1px solid #d7dce1;
          border-radius: 10px; touch-action: none; display: block; }
.poly { fill: rgba(29,53,87,0.18); stroke: #1d3557; stroke-width: 2; }
.poly.cw { fill: rgba(230,57,70,0.18); stroke: #c92f3c; }
.vertex { fill: #1d3557; stroke: #fff; stroke-width: 1.5; cursor: grab; }
.vertex.cw { fill: #c92f3c; }
.vertex:active { cursor: grabbing; }
.arrow { stroke: #1d3557; stroke-width: 2; fill: none; marker-end: url(#arrowhead); }
.arrow.cw { stroke: #c92f3c; }
.readout { font: 700 15px ui-monospace, monospace; margin: .55rem 0; min-height: 1.4em; color: #1d3557; }
.readout.cw { color: #c92f3c; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; }
button { font: 600 14px system-ui, sans-serif; padding: .45rem .9rem; border: 1px solid #1d3557;
         background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
const NS = 'http://www.w3.org/2000/svg';
const svg = document.getElementById('canvas');
const readout = document.getElementById('readout');

// Starting vertices of a simple pentagon, in counter-clockwise screen order.
const START = [[70, 190], [250, 170], [220, 60], [110, 40], [50, 110]];
let pts = START.map((p) => p.slice());

// The shoelace sum: half the sum of x_i * y_(i+1) minus x_(i+1) * y_i over all edges.
function signedArea(p) {
  let sum = 0;
  for (let i = 0; i < p.length; i++) {
    const [x1, y1] = p[i];
    const [x2, y2] = p[(i + 1) % p.length];
    sum += x1 * y2 - x2 * y1;
  }
  return sum / 2;
}

function centroid(p) {
  const cx = p.reduce((a, b) => a + b[0], 0) / p.length;
  const cy = p.reduce((a, b) => a + b[1], 0) / p.length;
  return [cx, cy];
}

function el(tag, attrs) {
  const e = document.createElementNS(NS, tag);
  for (const k in attrs) e.setAttribute(k, attrs[k]);
  return e;
}

function render() {
  svg.innerHTML = '';
  const defs = el('defs', {});
  const marker = el('marker', { id: 'arrowhead', markerWidth: '8', markerHeight: '8', refX: '6', refY: '3', orient: 'auto' });
  marker.appendChild(el('path', { d: 'M0,0 L6,3 L0,6 Z', fill: 'currentColor' }));
  defs.appendChild(marker);
  svg.appendChild(defs);

  // SVG y grows downward, so a negative shoelace sum draws clockwise on screen (flipped from standard math orientation).
  const area = signedArea(pts);
  const isCW = area < 0; // in SVG's y-down coordinates a negative shoelace sum draws clockwise on screen
  const cls = isCW ? 'cw' : '';

  const poly = el('polygon', {
    points: pts.map((p) => p.join(',')).join(' '),
    class: 'poly ' + cls
  });
  svg.appendChild(poly);

  // Draw a curved arrow around the centroid whose direction matches the polygon's winding.
  const [cx, cy] = centroid(pts);
  const r = 26;
  const a0 = -Math.PI / 2;
  const sweep = isCW ? 1 : 0;
  const a1 = a0 + (isCW ? 1 : -1) * (Math.PI * 1.5);
  const x0 = cx + r * Math.cos(a0), y0 = cy + r * Math.sin(a0);
  const x1 = cx + r * Math.cos(a1), y1 = cy + r * Math.sin(a1);
  const large = 1;
  const arrow = el('path', {
    d: `M ${x0} ${y0} A ${r} ${r} 0 ${large} ${sweep} ${x1} ${y1}`,
    class: 'arrow ' + cls
  });
  svg.appendChild(arrow);

  pts.forEach((p, i) => {
    const v = el('circle', { cx: p[0], cy: p[1], r: 8, class: 'vertex ' + cls, 'data-i': i });
    v.addEventListener('pointerdown', (ev) => startDrag(ev, i));
    svg.appendChild(v);
  });

  const label = isCW ? 'clockwise' : 'counter-clockwise';
  readout.textContent = fmt('Area = %area% | winding: %dir%', { area: Math.abs(area).toFixed(1), dir: label });
  readout.className = 'readout ' + cls;
}

// Pointer events let the user grab and move a single vertex; coordinates are re-scaled from screen pixels to the SVG viewBox.
let dragIndex = null;
function startDrag(ev, i) {
  dragIndex = i;
  ev.target.setPointerCapture(ev.pointerId);
}
svg.addEventListener('pointermove', (ev) => {
  if (dragIndex === null) return;
  const rect = svg.getBoundingClientRect();
  const scaleX = 320 / rect.width, scaleY = 240 / rect.height;
  let x = (ev.clientX - rect.left) * scaleX;
  let y = (ev.clientY - rect.top) * scaleY;
  x = Math.max(8, Math.min(312, x));
  y = Math.max(8, Math.min(232, y));
  pts[dragIndex] = [x, y];
  render();
});
svg.addEventListener('pointerup', () => { dragIndex = null; });
svg.addEventListener('pointerleave', () => { dragIndex = null; });

document.getElementById('reverse').addEventListener('click', () => {
  pts.reverse();
  render();
});
document.getElementById('shuffle').addEventListener('click', () => {
  // Nudge every vertex by a small random offset to demonstrate the formula updating live.
  const jitter = () => (Math.random() - 0.5) * 40;
  pts = pts.map(([x, y]) => [
    Math.max(8, Math.min(312, x + jitter())),
    Math.max(8, Math.min(232, y + jitter()))
  ]);
  render();
});
document.getElementById('reset').addEventListener('click', () => {
  pts = START.map((p) => p.slice());
  render();
});

render();
Result
Console

Watch what happens if you drag a vertex across an edge until the outline crosses itself, or if you relabel the vertices in the opposite order: the magnitude of the area barely changes, but the sign flips. That sign is not a bug — it is the formula telling you the polygon's orientation.

The Real Complexity

The formula for a polygon with vertices (x1,y1),…,(xn,yn)(x_1, y_1), \dots, (x_n, y_n), taken in order and wrapping back to the start, is:

A=12∣∑i=1n(xiyi+1−xi+1yi)∣A = \frac{1}{2}\left|\sum_{i=1}^{n} (x_i y_{i+1} - x_{i+1} y_i)\right|

Each term xiyi+1−xi+1yix_i y_{i+1} - x_{i+1} y_i is the 2D cross product of consecutive vertices (treated as vectors from the origin) — it is exactly twice the signed area of the triangle formed by the origin and that edge. Summing every edge's contribution and dividing by two, the parts outside the polygon cancel out and only the polygon's own area survives. That is the whole proof: no calculus, just triangle areas that telescope.

Complexity-wise this is about as cheap as computational geometry gets: one pass over the nn vertices, nn multiplications and nn subtractions, so O(n)O(n) time and O(1)O(1) extra space. Compare that to triangulating an arbitrary simple polygon, which historically needed more careful O(nlog⁡n)O(n \log n) algorithms (or O(n)O(n) with the intricate polygon triangulation result) — the shoelace sum gets the area alone without ever building a triangulation.

Drop the absolute value and the sign becomes meaningful: a positive sum means the vertices run counter-clockwise, a negative sum means clockwise. That single bit is exactly the orientation test used all over geometry — deciding whether a turn is a left turn or a right turn, which is also the core primitive behind algorithms like convex hull construction.

Where It Matters

A formula that turns raw coordinates into exact area and orientation shows up wherever shapes are represented as vertex lists:

  • Geographic Information Systems (GIS): parcel boundaries and country borders are polygons of latitude/longitude points; the shoelace sum (adapted for the sphere) is how their area gets computed.
  • CAD and manufacturing: cutting a part from sheet metal or estimating material usage starts from the same signed-area calculation on the part's outline.
  • Computer graphics: renderers use the sign of this cross-product sum to decide whether a polygon faces the camera (backface culling) before drawing a single pixel.
  • Physics and robotics: computing a rigid body's centroid and moment of inertia from its boundary reuses the same family of edge sums.

Once you have seen the shoelace formula, the same "sum of cross products along the boundary" idea keeps reappearing — it is close cousin to the orientation tests used in convex hull algorithms and to deciding point-in-polygon membership.

Conclusion

The shoelace formula is a small, complete story: take the coordinates you already have, cross-multiply them in a zigzag pattern, and out comes the exact area — with the sign thrown in for free to tell you which way the boundary turns.

It is a good reminder that not every geometric question is hard. Alongside puzzles like P vs NP that may never have an efficient algorithm, here is a problem that has had one, in closed form, since the 19th century — you just have to know where to lace the thread.

Share this article

Pick a channel — or use your device's native share sheet.

Comments

Loading comments...

https://www.kipuhub.com/en/article/shoelace-formula/Content licensed under CC BY-NC 4.0.