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

poople

v0.1.0

Published

A tiny, dependency-free TypeScript word ladder engine: build a word graph from any dictionary and find the shortest ladder between two words with breadth-first search.

Readme

poople

A tiny, dependency-free TypeScript word ladder engine.

Find the shortest path between two words by changing one letter at a time, using any dictionary you supply.

npm version license CI


A word ladder (also called Doublets, invented by Lewis Carroll in 1877) is a sequence of words where each step changes exactly one letter and every intermediate word is a real dictionary word. For example: cold → cord → card → ward → warm. This library finds the shortest such ladder between any two words using breadth-first search over a word graph you build from any dictionary you provide.

See it in action in the daily Poople.


Features

  • Zero runtime dependencies
  • Full TypeScript types included, ships as ESM
  • Bring-your-own-dictionary: no bundled word list, works with any language or domain
  • Single shortest path via BFS
  • All shortest paths via multi-parent BFS with an optional cap
  • Whole-graph distance map for computing "par" scores across an entire puzzle set

Install

npm i poople
pnpm add poople
yarn add poople

Quick start

import { buildDictionary, shortestPath, WordLadder } from "poople";

// Build a dictionary from any iterable of strings.
const words = ["cold", "cord", "card", "ward", "warm", "bold", "bard"];
const dict = buildDictionary(words);

// Find the shortest word ladder between two words.
const path = shortestPath("cold", "warm", dict);
console.log(path);
// => ["cold", "cord", "card", "ward", "warm"]

// Or use the WordLadder class for an ergonomic, stateful wrapper.
const ladder = new WordLadder(words);

console.log(ladder.shortestPath("cold", "warm"));
// => ["cold", "cord", "card", "ward", "warm"]

// Compute the distance from every reachable word to a target.
// Useful for setting a "par" score for the whole puzzle set.
const distances = ladder.distancesTo("warm");
console.log(distances.get("cold")); // => 4
console.log(distances.get("cord")); // => 3

API reference

Types

| Export | Description | |---|---| | Dictionary | ReadonlySet<string>, the normalized set of legal words |

Functions

buildDictionary(words: Iterable<string>): Dictionary

Normalizes each word (trim + lowercase), drops empty strings, and returns a ReadonlySet<string>.

getNeighbors(word: string, dict: Dictionary): string[]

Returns every word in dict that differs from word by exactly one letter. Input is lowercased automatically.

oneLetterDifferent(a: string, b: string): boolean

Returns true if a and b are the same length and differ in exactly one character position. Case-insensitive.

isValidStep(guess: string, prev: string, dict: Dictionary): boolean

Returns true if guess is present in dict and differs from prev by exactly one letter. Useful for validating player moves in a word ladder game.

shortestPath(start: string, end: string, dict: Dictionary): string[] | null

BFS shortest path from start to end. Returns an inclusive array of words (both endpoints included), or null if either word is absent from the dictionary or no ladder exists. Returns [start] when start === end. All words in the result are lowercase.

enumerateShortestPaths(start: string, end: string, dict: Dictionary, cap?: number): string[][]

Returns all shortest ladders from start to end, up to cap results (default 20). Uses multi-parent BFS to discover every shortest-path predecessor, then backtracks to reconstruct all routes. Returns an empty array if either word is unknown or the pair is unreachable. Returns [[start]] when start === end.

computeDistances(target: string, dict: Dictionary): Map<string, number>

BFS flood from target outward across the entire word graph. Returns a Map<string, number> where each key is a word reachable from target and the value is the minimum number of steps to reach target. The target itself maps to 0. Words unreachable from target are absent from the map. Use this to pre-compute the "par" (optimal step count) for every possible puzzle start word in a given dictionary.

Class: WordLadder

An ergonomic wrapper that holds a dictionary and exposes all operations as methods.

const ladder = new WordLadder(words: Iterable<string>)

| Member | Signature | Description | |---|---|---| | size | number | Number of words in the dictionary | | has | (word: string) => boolean | Whether word is in the dictionary (case-insensitive) | | neighbors | (word: string) => string[] | Words one letter away from word | | isValidStep | (guess: string, prev: string) => boolean | Whether guess is a legal next step from prev | | shortestPath | (start: string, end: string) => string[] \| null | BFS shortest ladder, or null | | allShortestPaths | (start: string, end: string, cap?: number) => string[][] | All shortest ladders, up to cap (default 20) | | distancesTo | (target: string) => Map<string, number> | BFS distance map from every reachable word to target |

How it works

The library models the word puzzle as a graph where each node is a word and two nodes share an edge when the words differ by exactly one letter. shortestPath runs standard BFS over this graph, which guarantees the shortest-step solution.

enumerateShortestPaths uses multi-parent BFS: during layer-by-layer expansion, every node records all predecessors that reach it at the minimum depth, not just the first one found. After the layer containing the target word is fully expanded, the algorithm backtracks through the parent map with a depth-first traversal to reconstruct all distinct shortest routes. Collection stops once cap paths are gathered, preventing exponential blowup on highly connected graphs.

computeDistances is a single BFS flood starting from the target word. Because BFS expands level by level, every word is assigned its exact minimum distance the first time it is reached. One call covers the entire connected component, making it efficient for bulk "par" precomputation.

Live demo

This engine powers the daily Poople word ladder game, a four-letter word ladder game with a new puzzle every day.

License

MIT