Polygon Area
The area of a simple polygon with vertices
(x_0, y_0), (x_1, y_1), …, (x_{n-1}, y_{n-1})(taken in order around the boundary, no self-intersection) is given by the shoelace formula — also known as the surveyor’s formula or Gauss’s area formula:Area = (1/2) · |Σ_{i=0}^{n-1} (x_i · y_{i+1} − x_{i+1} · y_i)|where indices are taken modulon(sox_n = x_0,y_n = y_0). The signed version (without the absolute value) is positive for counterclockwise vertex orderings and negative for clockwise — making it an indicator of polygon winding orientation as well as area. The formula generalises gracefully: it computes area for convex and concave polygons alike (any simple polygon, regardless of convexity); it computes the centroid (centre of mass) by analogous sums; it generalises to oriented surface integrals of any closed curve via Green’s theorem. It is named “shoelace” because if you write the coordinates in two columns and cross-multiply diagonally — like lacing a shoe — the sum naturally falls out. Used pervasively in: GIS (geographic information systems compute the area of countries, lakes, and parcels this way); CAD (computer-aided design uses signed area to determine the inside-vs-outside of a polygon); computer graphics (winding-number-based fill rules use the signed area of sub-loops); and competitive programming (a one-linerO(n)for any polygon-area question).
1. Intuition — Triangles Fanning from the Origin
A child-friendly analogy: imagine you cut out a polygon from a piece of paper and want to compute its area. The polygon could be a square (easy: side²), a triangle (easy: base × height / 2), or an irregular pentagon (less easy). For the irregular case, the trick is to pick any point — say, the origin (0, 0) — and draw straight lines to each vertex. This decomposes the polygon into n triangles fanning out from the origin. Compute each triangle’s signed area (with a sign that says “did I lay this triangle on top of another or carve a notch out?”), and add them up. The signs cancel where the triangles overlap (covering the same physical area twice), and what’s left is the polygon’s actual area.
The mathematical content of the shoelace formula is exactly this: the signed area of triangle (0, 0), (x_i, y_i), (x_{i+1}, y_{i+1}) is (1/2) · (x_i · y_{i+1} − x_{i+1} · y_i) — which we recognise as (1/2) · cross product (see Vector Cross Product). Summing over all consecutive vertex pairs gives a signed total. For a CCW polygon, the triangles “stick out” in alignment with the boundary, contributing positively. For a CW polygon, all signs flip. For a concave polygon, some triangles’ cross products are negative — they’re triangles whose “third vertex” (the polygon vertex) lies “behind” the line from origin to the previous vertex. The negative cancels the positive of an earlier-computed triangle that double-covered that region.
The “shoelace” name comes from the visual presentation: write the coordinates as
x_0 y_0
x_1 y_1
x_2 y_2
...
x_{n-1} y_{n-1}
x_0 y_0 ← repeat first row at end
Multiply the down-right diagonals (x_i · y_{i+1}) and the down-left diagonals (x_{i+1} · y_i); subtract the latter from the former; halve and absolute-value. The two diagonal patterns crossing each other look like the criss-cross of shoelaces. (Etymology in Braden 1986, who attributes the formula to Meister 1769 with consolidation by Gauss in his collected works.)
The deeper mathematical justification is Green’s theorem: for a simple closed curve C enclosing region R,
Area(R) = (1/2) · ∮_C (x dy − y dx)
For a polygon, C is the polygonal boundary, and the line integral around each edge from (x_i, y_i) to (x_{i+1}, y_{i+1}) evaluates to (1/2) · (x_i · y_{i+1} − x_{i+1} · y_i). Summing over edges gives the shoelace formula. So the formula is not a “trick” — it’s the discretisation of a fundamental result in vector calculus.
2. Tiny Worked Example — Convex and Concave
2.1 Convex Pentagon
Vertices in CCW order:
P_0 = (0, 0)
P_1 = (4, 0)
P_2 = (5, 3)
P_3 = (2, 5)
P_4 = (-1, 3)
Compute the shoelace sum:
S = (x_0 y_1 − x_1 y_0) + (x_1 y_2 − x_2 y_1) + (x_2 y_3 − x_3 y_2) + (x_3 y_4 − x_4 y_3) + (x_4 y_0 − x_0 y_4)
= (0·0 − 4·0) + (4·3 − 5·0) + (5·5 − 2·3) + (2·3 − (−1)·5) + ((−1)·0 − 0·3)
= 0 + 12 + (25 − 6) + (6 + 5) + 0
= 0 + 12 + 19 + 11 + 0
= 42
Area = |42| / 2 = 21.
Sanity check: the bounding box is [−1, 5] × [0, 5] = 6 × 5 = 30. Our pentagon clearly takes up more than half but less than all of this box — 21 / 30 = 0.7, plausible for a pentagon that mostly fills the box.
2.2 Concave Polygon (the “Pac-Man Mouth” Test)
Vertices in CCW order, with a triangular notch cut out:
Q_0 = (0, 0)
Q_1 = (6, 0)
Q_2 = (6, 6)
Q_3 = (3, 3) ← the indentation; pulls the boundary inward
Q_4 = (0, 6)
Shoelace sum:
S = (0·0 − 6·0) + (6·6 − 6·0) + (6·3 − 3·6) + (3·6 − 0·3) + (0·0 − 0·6)
= 0 + 36 + (18 − 18) + (18 − 0) + 0
= 0 + 36 + 0 + 18 + 0
= 54
Area = |54| / 2 = 27.
Sanity check: a 6 × 6 square has area 36. We cut out a triangle with vertices (6, 6), (3, 3), (0, 6) — that triangle has area (1/2) · base · height = (1/2) · 6 · 3 = 9. So 36 − 9 = 27. Matches.
The shoelace formula handles the concavity automatically because the signed sub-area computation around vertex Q_3 is negative — the cross-product (x_2 y_3 − x_3 y_2) = 18 − 18 = 0 and (x_3 y_4 − x_4 y_3) = 18 contributing positively — when traversed in CCW order with the indent. The concavity manifests as a smaller-than-bounding-box result, no special-casing needed.
2.3 Clockwise Order — Sign Inverts
Same pentagon as §2.1 but listed CW:
P_0 = (0, 0), P_1 = (-1, 3), P_2 = (2, 5), P_3 = (5, 3), P_4 = (4, 0)
Shoelace sum:
S = (0·3 − (−1)·0) + ((−1)·5 − 2·3) + (2·3 − 5·5) + (5·0 − 4·3) + (4·0 − 0·0)
= 0 + (−5 − 6) + (6 − 25) + (0 − 12) + 0
= 0 − 11 − 19 − 12 + 0
= −42
Same magnitude, opposite sign. Area = |−42| / 2 = 21. The sign tells you the orientation: positive ⇒ CCW, negative ⇒ CW.
3. Pseudocode — The Two Common Forms
3.1 The “Straightforward” Form
function polygon_area(P):
n := length(P)
if n < 3:
return 0
s := 0
for i := 0 to n − 1:
x_i, y_i := P[i]
x_next, y_next := P[(i + 1) mod n]
s := s + x_i * y_next − x_next * y_i
return |s| / 2
3.2 The “Cross-Product” Form (Equivalent, More Geometric)
function polygon_area_cross(P):
n := length(P)
if n < 3:
return 0
s := 0
origin := (0, 0)
for i := 0 to n − 1:
s := s + cross(origin, P[i], P[(i + 1) mod n])
return |s| / 2
function cross(o, a, b):
return (a.x − o.x) * (b.y − o.y) − (a.y − o.y) * (b.x − o.x)
These compute exactly the same numerical sum (the cross-product around the origin simplifies to x_i · y_{i+1} − x_{i+1} · y_i); use whichever form is more readable. The cross-product form makes the connection to Vector Cross Product explicit and is preferred when reasoning about the formula.
3.3 The “Anchored at Vertex 0” Form (Numerically Better)
function polygon_area_anchored(P):
n := length(P)
if n < 3:
return 0
s := 0
for i := 1 to n − 2:
s := s + cross(P[0], P[i], P[i + 1])
return |s| / 2
This anchors the triangulation at P[0] instead of the origin, giving n − 2 triangles. Numerically more stable for floating-point inputs whose coordinates are far from zero (e.g., GPS coordinates ≈ 10^6 meters from a chosen origin), because the cross-product of (P[i] − P[0]) and (P[i+1] − P[0]) works with smaller intermediate magnitudes. For integer inputs, all three forms are bit-exact equivalent.
3.4 Centroid (Centre of Mass) of a Polygon
function polygon_centroid(P):
n := length(P)
A := signed_area(P) # without abs, includes sign
cx := 0
cy := 0
for i := 0 to n − 1:
x_i, y_i := P[i]
x_next, y_next := P[(i + 1) mod n]
cross_term := x_i * y_next − x_next * y_i
cx := cx + (x_i + x_next) * cross_term
cy := cy + (y_i + y_next) * cross_term
cx := cx / (6 * A)
cy := cy / (6 * A)
return (cx, cy)
The centroid is the weighted average of triangle centroids, weighted by signed triangle area. Derivation in Wikipedia: Centroid of a polygon and O’Rourke 1998.
4. Python Implementation
from typing import Sequence
Point = tuple[int, int]
def signed_polygon_area_doubled(pts: Sequence[Point]) -> int:
"""
2 * signed area of the simple polygon defined by `pts` (in order).
Positive for CCW vertex order, negative for CW.
Returns 2A so the result is exactly integer for integer coordinate inputs
— avoids the / 2 which can introduce a half-integer.
O(n) time, O(1) space.
"""
n = len(pts)
if n < 3:
return 0
s = 0
for i in range(n):
x1, y1 = pts[i]
x2, y2 = pts[(i + 1) % n]
s += x1 * y2 - x2 * y1
return s
def polygon_area(pts: Sequence[Point]) -> float:
"""
Unsigned area of a simple polygon.
O(n) time.
"""
return abs(signed_polygon_area_doubled(pts)) / 2
def polygon_orientation(pts: Sequence[Point]) -> str:
"""
'CCW' if the polygon is given in counterclockwise order, 'CW' if clockwise,
'COLLINEAR' if all points are collinear (zero area).
"""
s = signed_polygon_area_doubled(pts)
if s > 0:
return "CCW"
if s < 0:
return "CW"
return "COLLINEAR"
def polygon_centroid(pts: Sequence[Point]) -> tuple[float, float]:
"""
Centroid (centre of mass) of a uniformly-dense simple polygon.
Uses the signed-area-weighted formula. Polygon is assumed simple
(no self-intersection); result is undefined for self-intersecting input.
"""
n = len(pts)
if n < 3:
raise ValueError("polygon must have at least 3 vertices")
a = 0
cx = 0.0
cy = 0.0
for i in range(n):
x1, y1 = pts[i]
x2, y2 = pts[(i + 1) % n]
cross_term = x1 * y2 - x2 * y1
a += cross_term
cx += (x1 + x2) * cross_term
cy += (y1 + y2) * cross_term
a /= 2.0 # signed area
if a == 0:
raise ValueError("degenerate polygon: zero signed area")
cx /= 6 * a
cy /= 6 * a
return cx, cy
def polygon_area_anchored(pts: Sequence[Point]) -> float:
"""
Numerically more stable formulation that anchors triangles at pts[0]
instead of the origin. For integer inputs this is bit-equivalent to
the standard formula; for float inputs with large absolute coordinates
this loses fewer digits of precision.
"""
n = len(pts)
if n < 3:
return 0.0
x0, y0 = pts[0]
s = 0
for i in range(1, n - 1):
x1, y1 = pts[i]
x2, y2 = pts[i + 1]
# cross product of (P[i] - P[0]) and (P[i+1] - P[0])
s += (x1 - x0) * (y2 - y0) - (y1 - y0) * (x2 - x0)
return abs(s) / 2.0A few implementation notes:
signed_polygon_area_doubledreturns2A, notA. This is deliberate: for integer-coordinate inputs,2Ais exactly integer, whileAmay be a half-integer (e.g., a triangle with vertices(0,0), (1,0), (0,1)hasA = 0.5). Returning2Alets downstream code stay in integer arithmetic for as long as possible.- Use
(i + 1) % nto wrap around the last vertex to the first. Forgetting this drops the last edge’s contribution, giving wrong area. - The centroid formula divides by
6A(not2A). Two factors of2come from the cross-product (= 2 · signed triangle area); one factor of3from “centroid of triangle is the average of the three vertex positions” (only the(x_i + x_next)part appears because the third vertex is the origin). It’s not a typo — see Bourke 1988 for the derivation. polygon_area_anchoredis preferred when input coordinates are floats with large magnitudes. For integer inputs this should be irrelevant.
5. Complexity
- Time:
O(n)for any of the formulations. Each vertex contributesO(1)arithmetic operations: two multiplications, one subtraction, one addition. - Space:
O(1)working memory (just a running sum). The input polygon takesO(n)space, but that’s the input itself.
This is information-theoretically optimal: any algorithm that computes the area must read every vertex, since changing a single vertex changes the area in general. So Ω(n) is a tight lower bound.
5.1 Bit Width for Integer Inputs
For polygon vertices with |x|, |y| ≤ M:
- Each cross-product term
x_i · y_{i+1} − x_{i+1} · y_iis at most2M²in magnitude. - The sum over
nterms is at most2 · n · M². - For
M = 10^9andn = 10^5, the sum can reach2 · 10^{23}— overflowsint64(which tops out at~9.2 · 10^{18}).
Implication: for large-coordinate, many-vertex inputs, use int128 in C++ or Python’s arbitrary-precision integers. Or scale down coordinates by a factor of, say, 10^3 if precision permits.
5.2 Sensitivity to Vertex Ordering
The formula assumes vertices are given in traversal order along the boundary. If they’re scrambled, the formula computes the signed area of an arbitrary self-intersecting polyline — which is not the same as the polygon’s “set-theoretic area” (the area of the region of points enclosed by the boundary). For self-intersecting input the formula computes the “winding number weighted area” — useful in some contexts (CAD even-odd fill rules), wrong in others. Always validate that input is a simple polygon if the application requires the set-theoretic interpretation.
6. Variants and Sub-Patterns
6.1 Pick’s Theorem — Lattice Polygon Area in Closed Form
For a polygon whose vertices have integer coordinates (a “lattice polygon”), Pick’s theorem (Pick 1899) gives:
A = i + b/2 − 1
where i is the number of integer lattice points strictly inside the polygon and b is the number of integer lattice points on the boundary. Combining with the shoelace formula gives a way to count interior lattice points without enumeration:
i = A − b/2 + 1
b = Σ_{edges} gcd(|x_{i+1} − x_i|, |y_{i+1} − y_i|)
Useful in competitive programming whenever the question “how many lattice points lie inside this polygon” comes up.
6.2 Signed Area for Self-Intersecting Polygons
If the polygon is self-intersecting (e.g., a “figure-8”), the shoelace formula computes a winding-number weighted signed area: each region is multiplied by its winding number. For a figure-8 with one CCW lobe and one CW lobe of equal area, the formula returns 0. For a star polygon (5-pointed star traversed CCW), the formula returns the area of the central pentagon counted twice plus the five points counted once. This is occasionally the desired semantics (CAD), but for the “set-theoretic area of the region enclosed”, you must first decompose the input into simple polygons (Bentley-Ottmann segment intersection, see Line Sweep) and apply the formula to each.
6.3 Spherical Polygon Area (GIS Application)
For polygons on a sphere (e.g., country boundaries on the Earth), the planar shoelace formula is wrong. The right formula uses L’Huilier’s theorem for spherical excess, or for many small triangles, the planar shoelace on a local equal-area projection (e.g., Albers conic). GIS libraries (PostGIS, GEOS, Shapely with pyproj) handle this transparently by reprojecting to an equal-area frame before computing.
6.4 Continuous (Calculus) Area via Green’s Theorem
For an arbitrary closed curve C enclosing region R:
Area(R) = (1/2) · ∮_C (x dy − y dx)
= ∮_C x dy
= -∮_C y dx
For a polygon, this discretises to the shoelace formula. For a smooth curve given parametrically (x(t), y(t)), one numerically integrates by trapezoidal rule, which (after simplification) is exactly the shoelace formula on the sampled vertices.
6.5 3D Polygon Area
For a planar polygon embedded in 3D (vertices (x_i, y_i, z_i) all in some plane), the area is the magnitude of the 3D cross-product sum:
Area = (1/2) · | Σ_{i=0}^{n-1} (P_i − P_0) × (P_{i+1} − P_0) |
where × is the full 3D cross product. The result is a vector whose magnitude is twice the area; the direction is the polygon’s normal. Used in 3D graphics (face culling, normal computation, ray-polygon intersection).
For a non-planar polygon (e.g., a quadrilateral whose 4 vertices are not coplanar), “area” is ambiguous — it depends on which surface you choose to fill the polygon. The 3D-cross-product-sum gives a useful default (the “vector area”) that is well-defined for any polygon.
6.6 Monte Carlo Estimation (Pedagogical)
Randomly sample N points uniformly from the bounding box and count how many fall inside the polygon (using a point-in-polygon test):
Area ≈ (count_inside / N) · area_of_bounding_box
Convergence is O(1/√N) (slow). Useful only when the polygon is so complex (e.g., billions of vertices, or a fractal boundary) that a deterministic algorithm is infeasible.
7. Diagram — Triangle Fan and Sign Cancellation
flowchart LR O((origin)) -. triangle 0 .-> P0((P0)) O -. triangle 1 .-> P1((P1)) O -. triangle 2 .-> P2((P2)) O -. triangle 3 .-> P3((P3)) O -. triangle 4 .-> P4((P4)) P0 --> P1 P1 --> P2 P2 --> P3 P3 --> P4 P4 --> P0 P0 --> SUM[signed sum of cross products] P1 --> SUM P2 --> SUM P3 --> SUM P4 --> SUM SUM --> A[Area = |sum| / 2] style O stroke:#5af style SUM stroke:#5a5
What this diagram shows. The central point (blue, origin) is the apex of a triangle fan; the polygon vertices P_0, P_1, …, P_4 form the “outer edge” of the fan. Each triangle O-P_i-P_{i+1} contributes a signed area (positive if P_i, P_{i+1} traverse the apex counterclockwise, negative if clockwise). Solid arrows around the bottom (P_0 → P_1 → … → P_4 → P_0) show the polygon boundary in vertex order. Dashed lines from the origin to each vertex show how the polygon decomposes into triangles. Each triangle’s signed area is (1/2) · cross(O, P_i, P_{i+1}), computed via Vector Cross Product. The green box (signed sum) accumulates these signed areas; positive contributions and negative contributions cancel where triangles overlap (which happens for concave polygons or polygons that don’t surround the origin). The final box (Area) takes the absolute value and divides by 2. Key insight conveyed by the diagram: the formula doesn’t care whether the origin is inside or outside the polygon — sign cancellation handles both cases uniformly. This is why the same O(n) one-liner works for arbitrary simple polygons.
8. Pitfalls
-
Forgetting to wrap the last vertex. The last edge of the polygon is
P_{n-1} → P_0. If you writefor i in range(n - 1), you miss it. Use(i + 1) % nor explicitly add the closing termx_{n-1} · y_0 − x_0 · y_{n-1}after the loop. -
Integer overflow. As noted in §5.1, products of
int32coordinates can overflow evenint64for large polygons (n · M²). Useint128or Python ints for safety. C++long longoverflows silently and produces wildly wrong areas — a particularly nasty bug because the computed value is plausibly small instead of crashing. -
Treating self-intersecting polygons as simple. The formula computes a winding-number-weighted signed area for self-intersecting input. For “the area of the set of enclosed points”, you must first decompose the input into simple polygons (see Line Sweep §6.4). This bug is common in CAD-import code that doesn’t validate input.
-
Wrong sign convention in centroid formula. The centroid uses signed area (with sign), not the absolute value. If you use
|A|, you get the wrong centroid for CW-ordered polygons. Use the signed area in the centroid formula and sign-correct only at the end if needed. -
Using floats for integer-coordinate inputs. Loses precision for no benefit. The shoelace formula is exact in integer arithmetic; promoting to float only introduces round-off.
-
Centroid of a polygon vs centroid of its vertices. The centroid of the polygon (as a 2D region) is not the average of its vertex coordinates. The vertex-average is the “vertex centroid”, which equals the polygon centroid only for very symmetric shapes (a regular polygon). Common interview gotcha: “compute the centre of a polygon” — clarify which centre the asker means.
-
Negative area treated as a bug. A negative result from
signed_polygon_area_doubledsimply means the input is in CW order. Don’t flag this as an error — just take the absolute value. (Or, if your application cares about orientation, use the sign as the orientation indicator.) -
Using the planar formula for spherical (geographic) polygons. A country’s area on Earth’s surface cannot be computed by treating
(latitude, longitude)as(y, x)and applying the shoelace formula — the answer is wrong by a factor ofcos(latitude)and worse near the poles. Use a proper geographic CRS (coordinate reference system) and reproject to an equal-area projection first. -
Vertex order ambiguity when the polygon is given as a set of edges. If the input is a list of edges (segments) rather than an ordered vertex list, you must first reconstruct the ordered traversal. This is a graph problem (Eulerian circuit on the polygon’s boundary), not a geometry problem; don’t apply the shoelace formula until you have the right ordering.
-
Confusing “polygon area” with “point-in-polygon” or “polygon-polygon intersection area”. All three are different problems. The shoelace formula is for the first. For the second, use the ray-casting or winding-number algorithm. For the third (intersection area of two polygons), use polygon clipping (Sutherland-Hodgman, Weiler-Atherton).
-
Centroid undefined for zero-area polygons. If all vertices are collinear, the polygon has zero area and the centroid is undefined (the formula divides by
0 · A). Validate before computing. -
Floating-point cancellation for nearly-degenerate inputs. If two vertices are very close,
x_i · y_{i+1} − x_{i+1} · y_ican be the subtraction of two nearly-equal large floats — catastrophic cancellation. Usepolygon_area_anchored(anchor at vertex 0, work with differences) for better numerical conditioning. Or use rational arithmetic / exact predicates.
9. Common Interview Problems
| Problem | Source | Notes |
|---|---|---|
| Compute area of a polygon | Direct application | One-liner if input is given correctly |
| Largest Rectangle in Histogram | LeetCode 84 | Different problem (1D), but uses analogous “shoelace” intuition |
| Erect the Fence | LeetCode 587 | Convex hull; verify CCW orientation via signed area |
| Convex Polygon | LeetCode 469 | Verify all consecutive triples have same cross-product sign — equivalent to checking the signed area is monotonic in some sense |
| Is Triangle Inside / Outside Polygon | Geometry interview | Use signed area or ray-casting |
| Rectangle Area II | LeetCode 850 | Sum of rectangles — but with overlaps; needs Line Sweep, not just shoelace |
| Largest Triangle Area | LeetCode 812 | signed_triangle_area_doubled / 2, see Vector Cross Product |
| Self-Crossing | LeetCode 335 | Test path for self-intersection; uses signed-area-like reasoning |
| Calculator-style: count “regions” of a polygon arrangement | Application | Euler’s formula V − E + F = 2 plus shoelace per face |
| Centroid of polygon | Application | Use centroid formula; common in physics simulations and CAD |
10. Open Questions
- For polygons with
n = 10^9vertices (e.g., outline of a complex coastline at high resolution), is there a hierarchical / approximate algorithm that beatsO(n)? Asymptotically no (you must read each vertex), but constants matter — a SIMD-vectorised inner loop can be 4–8× faster than the scalar version. CGAL and GEOS use specialised SIMD kernels. - What’s the right way to define “area” for polygons given in projective or homogeneous coordinates (e.g., as outputs of a perspective camera projection)? The straightforward extension uses the cross-product formula in homogeneous coords, but the answer depends on the projective frame.
- Pick’s theorem (§6.1) says area + half-boundary-points − 1 = interior lattice points. Is there a fast algorithm to compute interior lattice points of a polygon given the shape but not the lattice points themselves? (Answer: yes — combine the shoelace formula and the gcd-based boundary count.) But for non-lattice polygons, lattice-point counting is
Θ(area)worst case (Lehmer’s conjecture-related, AFAICT). - Numerical robustness: for floating-point input vertices, when does the shoelace formula’s result differ noticeably from the true area? Shewchuk 1996 discusses adaptive precision for orientation tests; the area-formula extension is straightforward but rarely implemented in textbooks.
11. See Also
- Vector Cross Product — the underlying primitive; signed triangle area = (1/2) cross product
- Convex Hull - Graham Scan — convex polygons; use signed area to verify CCW orientation
- Convex Hull - Andrew’s Monotone Chain — sibling hull algorithm
- Closest Pair of Points — sibling computational-geometry primitive
- Line Sweep — needed for area-of-rectangle-union, polygon-polygon-intersection, and self-intersecting input decomposition
- Master Theorem — irrelevant here (no recurrence) but used in many sibling D&C algorithms
- Big-O Notation
- SWE Interview Preparation MOC