npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

voting-engine

v0.1.0

Published

A TypeScript library for implementing and running voting systems: plurality, approval, ranked-choice (IRV), Borda count, Condorcet (Copeland), and single transferable vote (STV).

Readme

voting-engine

A TypeScript library for implementing and running voting systems. Ships six well-known methods and a shared type system, so you can swap methods without rewriting your ballot-handling code.

  • Plurality (first-past-the-post) — single choice, most votes wins
  • Approval — approve any number of candidates, most approvals wins
  • Borda Count — points by rank position, most points wins
  • Instant-Runoff Voting (ranked-choice / RCV) — round-by-round elimination until a majority forms
  • Condorcet (Copeland) — pairwise head-to-head comparisons, with cycle fallback
  • Single Transferable Vote (STV) — multi-winner, proportional, with fractional surplus transfer

Install

npm install voting-engine

Quick start

import { PluralityVoting, type Candidate, type SingleChoiceBallot } from "voting-engine";

const candidates: Candidate[] = [
  { id: "alice", name: "Alice" },
  { id: "bob", name: "Bob" },
];

const ballots: SingleChoiceBallot[] = [
  { choice: "alice" },
  { choice: "alice" },
  { choice: "bob" },
];

const result = new PluralityVoting().tally(candidates, ballots);
console.log(result.winner); // "alice"
console.log(result.tally);  // { alice: 2, bob: 1 }

Every method implements the same interface:

interface VotingSystem<TBallot extends Ballot> {
  readonly name: string;
  tally(candidates: Candidate[], ballots: TBallot[]): VotingResult;
}

So you can pick a method at runtime and reuse the same calling code, as long as you feed it the matching ballot shape.

Ranked-choice (instant-runoff) example

import { RankedChoiceVoting, type RankedBallot } from "voting-engine";

const candidates = [{ id: "A" }, { id: "B" }, { id: "C" }];
const ballots: RankedBallot[] = [
  ...Array(45).fill({ ranking: ["A", "B", "C"] }),
  ...Array(30).fill({ ranking: ["B", "C", "A"] }),
  ...Array(25).fill({ ranking: ["C", "B", "A"] }),
];

const result = new RankedChoiceVoting().tally(candidates, ballots);
console.log(result.winner); // "B" — C is eliminated first, its votes flow to B
console.log(result.rounds); // full round-by-round breakdown

Multi-winner example (STV)

import { SingleTransferableVote, type RankedBallot } from "voting-engine";

const candidates = [{ id: "A" }, { id: "B" }, { id: "C" }, { id: "D" }];
const ballots: RankedBallot[] = [
  /* ... ranked ballots ... */
];

const result = new SingleTransferableVote({ seats: 2 }).tally(candidates, ballots);
console.log(result.winners); // e.g. ["A", "B"]

Handling ties

Every method accepts an optional tieBreaker in its constructor. It receives the tied candidate ids and should return the subset to keep as winners (a single id to fully resolve the tie, or the same list back to leave it unresolved):

new PluralityVoting({
  tieBreaker: (tied) => [tied.sort()[0]], // alphabetical tiebreak
});

If a tie is never resolved, result.winner is null and the tied ids are listed in result.tiedCandidates.

Ballot types

Each method expects a specific ballot shape:

| Method | Ballot type | Shape | |---|---|---| | PluralityVoting | SingleChoiceBallot | { choice: CandidateId } | | ApprovalVoting | ApprovalBallot | { approved: CandidateId[] } | | BordaCount | RankedBallot | { ranking: CandidateId[] } | | RankedChoiceVoting | RankedBallot | { ranking: CandidateId[] } | | CondorcetVoting | RankedBallot | { ranking: CandidateId[] } | | SingleTransferableVote | RankedBallot | { ranking: CandidateId[] } |

ScoredBallot ({ scores: Record<CandidateId, number> }) is also exported for building your own score-based method (e.g. STAR voting or range voting) on top of the shared types and validation helpers.

Errors

  • UnknownCandidateError — a ballot references a candidate id not present in your candidate list
  • InvalidElectionError — structural problems (no candidates, duplicate ids, more STV seats than candidates)

Both extend VotingSystemError.

Building your own method

The shared helpers in the package (validateCandidates, zeroTally, findLeaders, findTrailers) are exported so you can implement additional methods (e.g. STAR voting, cumulative voting) consistent with the rest of the library — just implement the VotingSystem<TBallot> interface.

Scripts

npm run build       # bundle to dist/ (ESM + CJS + .d.ts)
npm test            # run the test suite (vitest)
npm run typecheck   # type-check without emitting

License

MIT