earcut: fast polygon triangulation for WebGL, and what it will not do for you
The fastest and smallest JavaScript polygon triangulation library for your WebGL apps
At a glance
- What is it?
- earcut is a 4KB JavaScript ear-slicing triangulator built for Mapbox GL JS and Three.js. It is fast on practical map data, and it deliberately does not guarantee a correct mesh on bad input.
- Who is it for?
- Adopt earcut when you are triangulating clean, non-self-intersecting polygon rings for rendering, and you can accept a non-conforming mesh. Do not adopt it when you need a guaranteed-correct triangulation on messy input, or a conforming mesh for navmesh or FEM work.
- Can I use it commercially?
- Yes. ISC is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 2 days ago.
- What is it written in?
- Mainly JavaScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 29, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What earcut solves, and who actually needs it
WebGL does not draw polygons. It draws triangles. If you have a polygon outline, whether it came from a GeoJSON boundary, a font glyph, a CAD export or a hand-drawn shape, something has to convert that outline into a flat list of triangle indices before the GPU can render it. earcut does exactly that one job, in 4KB gzipped, and it does it fast enough to run inside a browser frame budget.
The library was originally built for Mapbox GL JS, the WebGL-based interactive map renderer, and the README notes it is now also used by Three.js and many other projects. That origin explains the design. Map tiles contain hundreds of thousands of small polygons, most of them convex-ish and well behaved, and the renderer needs them triangulated continuously as the user pans and zooms. A triangulator that spends time proving geometric correctness on each tile is the wrong tool for that workload. A triangulator that produces a usable mesh in milliseconds is the right one.
So the audience is narrow and specific: graphics and mapping engineers who control their input data and care more about throughput than about guaranteed topology. If your polygons arrive from an untrusted source, or you need a mesh that downstream simulation code can trust, earcut is not the library you want, and the README says so plainly.
Ear slicing, z-order sorting and spatial hashing
The core algorithm is a modified ear slicing method. An ear is a triangle formed by three consecutive vertices of a polygon ring where the triangle lies inside the ring and contains no other vertex. Repeatedly clipping ears off a simple polygon eventually consumes the whole ring and yields a triangulation. That is the classical approach, and it is O(n^2) in its naive form because each ear test scans the remaining vertices.
earcut changes the constant factors. The README states the implementation is optimized by a z-order curve and spatial hashing, which is how it avoids rescanning the full vertex set for every ear test. It is based on ideas from Martin Held's FIST work and David Eberly's writing on ear clipping, with extensions for holes, twisted polygons, degeneracies and self-intersections. Those extensions are where the guarantee gets dropped: the README says the approach "doesn't guarantee correctness of triangulation, but attempts to always produce acceptable results for practical data."
The data flow is flat and index-based. Vertices arrive as a flat array of coordinates, holes are given as indices into that array, and the output is a flat array of vertex indices in groups of three. There is no object graph, no half-edge structure, no allocation of per-vertex records. That is why the library is small, and it is also why it cannot repair bad input: there is no topological model to repair against.
Installing earcut and triangulating a polygon with a hole
Install with npm. The package is published as an ES module, with the main entry pointing at src/earcut.js and a UMD bundle shipped in dist/ for script-tag use.
npm install earcutImport the default function, plus the named helpers if you need them. The README's import line exposes flatten, deviation and refine alongside the triangulator itself.
import earcut, {flatten, deviation, refine} from 'earcut';A first real call takes a flat coordinate array. The README's minimal example returns [1,0,3, 1,3,2] for a four-vertex input, and each group of three indices is one triangle.
const triangles = earcut([10,0, 0,50, 60,60, 70,10]); // returns [1,0,3, 1,3,2]For a polygon with a hole, pass the hole as an array of vertex indices. The README's example uses a 100x100 square with a 20-to-80 inner square, and the hole index 4 marks where the inner ring starts.
earcut([0,0, 100,0, 100,100, 0,100, 20,20, 80,20, 80,80, 20,80], [4]);If your data is a GeoJSON Polygon, flatten converts the nested coordinate arrays into the vertices, holes and dimensions triple the triangulator expects.
const data = flatten(geojson.geometry.coordinates);
const triangles = earcut(data.vertices, data.holes, data.dimensions);After triangulating, deviation gives you a number instead of an opinion. It returns the relative difference between the total triangle area and the input polygon area, and 0 means the triangulation is fully correct. Run it on a sample of your real data before you ship anything.
The refine pass, and why it is opt-in
Raw earcut output contains skinny triangles, because ear clipping has no reason to prefer well-shaped ones. If triangle quality matters, the library ships a separate refine function that legalizes interior edges with Delaunay flips while preserving the polygon boundary and holes.
const triangles = earcut(vertices, holes, dimensions);
refine(triangles, vertices, dimensions);The README states that refine mutates the triangles array in place, keeps the same number of triangles and the same index format, and usually removes many skinny triangles while reducing total triangle edge length. It is a post-process, so it costs nothing unless you call it. The README's benchmark numbers reflect that: triangulating 119,680 real-world polygons (1.9M vertices) drawn from map tiles across zooms 4 to 16 takes roughly 445 ms on a MacBook Pro M1 Pro (2021), with optional Delaunay refinement adding about 168 ms on top. Those figures come from the project's own benchmark, which you can run with npm run bench.
The constraint to note is what refine does not do. It assumes a valid manifold triangulation, such as the output of earcut, and the README says it does not repair invalid polygon input or make the mesh conforming. Refinement improves shape quality. It does not turn a broken input into a correct mesh.
Where earcut produces the wrong answer
The README is unusually direct about this. The input is assumed to be a valid polygon: rings that do not self-cross or overlap, holes that stay inside the outer ring, and no duplicate or zero-length edges. On input that breaks those assumptions, the result can be noticeably wrong, with overlapping triangles, gaps, or triangles outside the polygon.
That is a real failure mode, not a theoretical one. Map data, SVG paths and user-drawn shapes routinely contain self-intersections and duplicate vertices. The library will not throw. It will return an index array that looks plausible and renders incorrectly. If your pipeline has no validation step, you will find out visually, and possibly not until a user zooms into the wrong tile.
The second limitation is topological rather than geometric. The output is not conforming: a vertex may land in the middle of another triangle's edge, producing a T-junction. The README calls this harmless for rendering but notes it can break navmesh or FEM use, and points to removing T-junctions in a post-process. If you are building a navigation mesh or a finite element mesh, that single sentence rules earcut out of your core pipeline unless you add the post-process yourself. The README also notes that a single vertex passed as a hole is treated as a Steiner point, which is a useful trick but not a general-purpose repair mechanism.
earcut vs libtess.js: speed against guaranteed correctness
The README names its own alternative: libtess.js, for a guaranteed-correct triangulation even on bad data, described as slower and larger. The difference in approach is fundamental rather than incremental. libtess.js is a port of the OpenGL GLU tessellator, which maintains a topological sweep structure and can handle self-intersecting and degenerate input correctly. earcut drops that structure in exchange for a flat index array and a small bundle.
That trade shows up in three places at once. Bundle size: 4KB gzipped for earcut, larger for libtess.js. Throughput: the project's benchmark is built around the Mapbox Vector Tile workload, which is the shape of data earcut was tuned for. And correctness guarantees: earcut's README states outright that it does not guarantee a correct triangulation on arbitrary input, while libtess.js is offered as the option when correctness matters.
There is a middle path worth naming. If you control the input, you can clean it before it reaches earcut rather than switching libraries. Removing duplicate and zero-length edges, splitting self-intersecting rings, and verifying that holes sit inside the outer ring addresses exactly the assumptions the README lists. That keeps the 4KB bundle and the frame-time budget, at the cost of writing and maintaining the cleanup step. Whether that is cheaper than running libtess.js depends on how dirty your data actually is, and you can measure that with deviation.
Maintenance, licence and the cost of upgrading
The repository is not archived, and the last push was on 2026-09-22. Three patch releases landed in July 2026: v3.2.2 and v3.2.1 on 2026-07-01, and v3.2.3 on 2026-07-02. The project is published under the ISC licence, a permissive licence that permits use and redistribution provided the copyright notice and licence text are retained. That is a summary of the licence identifier, not legal advice; read the LICENSE file in the repository for the actual terms.
Upgrade cost is low by design. The package has no runtime dependencies, marked with sideEffects false, and exposes a single main entry with a TypeScript declaration file. The public surface is four functions: earcut, refine, flatten and deviation. Patch releases within the 3.2 line are unlikely to break callers, but the 3.x line did introduce the refine and deviation helpers and the named exports, so code written against an older 2.x import style will not carry over unchanged. The devDependencies list rollup, eslint, typescript and the terser plugin, which means building the dist bundles from source requires that toolchain, though consumers installing from npm do not need it.
The maintenance question that matters more than release cadence is whether the correctness contract changes. The README currently promises acceptable results on practical data and nothing more. If a future release tightened that promise, it would likely cost speed or size, and the benchmark in bench/ is where you would see it.
Editorial conclusion
Adopt earcut when you are triangulating clean, non-self-intersecting polygon rings for rendering, and you can accept a non-conforming mesh. Do not adopt it when you need a guaranteed-correct triangulation on messy input, or a conforming mesh for navmesh or FEM work. Before committing, run npm run bench on your own polygon set, and check deviation on a sample of your real data; a non-zero value tells you your input violates the assumptions the README lists.
Frequently asked questions
What is polygon triangulation?
It is the process of splitting a polygon into a set of triangles that together cover the same area. WebGL needs triangles rather than arbitrary polygons, which is why a triangulator like earcut sits between your polygon data and the GPU.
What is the ear clip algorithm and how does it work?
Ear clipping repeatedly removes an ear, a triangle formed by three consecutive ring vertices that lies inside the polygon and contains no other vertex, until the ring is consumed. earcut implements a modified version of this, optimized with a z-order curve and spatial hashing and extended to handle holes and degeneracies.
Why does earcut return wrong triangles on my polygon?
The README states that earcut does not guarantee a correct triangulation on arbitrary input, and assumes rings that do not self-cross or overlap, holes inside the outer ring, and no duplicate or zero-length edges. On input that breaks those assumptions the result can contain overlapping triangles, gaps or triangles outside the polygon. Clean the input first, or use a library that guarantees correctness.
How do I get Delaunay-quality triangles out of earcut?
Call the refine function after triangulating, passing the triangles array, the vertices and the dimensions. The README states that refine mutates the triangles array in place, legalizing interior edges with Delaunay flips while preserving the boundary and holes, and keeps the same triangle count and index format.
How do I check whether an earcut triangulation is correct?
Use the deviation function with the vertices, holes, dimensions and triangles. It returns the relative difference between the total triangle area and the input polygon area, and the README states that 0 means the triangulation is fully correct.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/mapbox-earcut)