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

dancing-links

v4.3.7

Published

Fastest JS solver for exact cover problems using Dancing Links

Readme

dancing-links

About

This is an implementation of Knuth's DLX to solve the exact cover problem. It is a port of Knuth's literate dancing links implementation and supports primary and secondary constraints, and returning custom data in addition to row indices.

There are no external dependencies and there is full TypeScript support.

It is currently the fastest Dancing Links implementation in JavaScript.

Usage

Basic Example

import { DancingLinks } from 'dancing-links'

const dlx = new DancingLinks<string>()
const solver = dlx.createSolver({ columns: 3 })

solver.addSparseConstraint('row1', [0, 2]) // Constraint active in columns 0 and 2
solver.addSparseConstraint('row2', [1]) // Constraint active in column 1
solver.addSparseConstraint('row3', [0, 1]) // Constraint active in columns 0 and 1

const solutions = solver.findAll()
// Returns: [[{ data: 'row1', index: 0 }, { data: 'row2', index: 1 }]]

Constraint Formats

Sparse Constraints (Recommended)

Sparse constraints are the most efficient format - specify only the active column indices instead of full binary arrays. This reduces parsing overhead and memory usage, especially for problems with many columns.

const solver = dlx.createSolver({ columns: 100 })

// ⚡ EFFICIENT: Only specify active columns
solver.addSparseConstraint('constraint1', [0, 15, 42, 87])
solver.addSparseConstraint('constraint2', [1, 16, 43, 99])

// Batch operations for better performance
const constraints = [
  { data: 'batch1', columnIndices: [0, 10, 20] },
  { data: 'batch2', columnIndices: [5, 15, 25] },
  { data: 'batch3', columnIndices: [2, 12, 22] }
]
solver.addSparseConstraints(constraints)

Binary Constraints

Use binary constraints when it's more convenient for your encoding logic or when you already have constraint data in binary format:

const solver = dlx.createSolver({ columns: 4 })

// Convenient when encoding naturally produces binary arrays
solver.addBinaryConstraint('row1', [1, 0, 1, 0])
solver.addBinaryConstraint('row2', [0, 1, 0, 1])
solver.addBinaryConstraint('row3', [1, 1, 0, 0])

// Batch operations
const binaryConstraints = [
  { data: 'batch1', columnValues: [1, 0, 1, 0] },
  { data: 'batch2', columnValues: [0, 1, 0, 1] }
]
solver.addBinaryConstraints(binaryConstraints)

Constraint Templates

For problems with reusable constraint patterns, templates provide significant performance benefits by pre-processing base constraints. Templates are especially beneficial when using binary constraints, as the binary-to-sparse conversion happens once during template creation rather than every time you create a solver:

// Create template with base constraints
const template = dlx.createSolverTemplate({ columns: 20 })
template.addSparseConstraint('base1', [0, 5, 10])
template.addSparseConstraint('base2', [1, 6, 11])

// Create multiple solvers from the same template
const solver1 = template.createSolver()
solver1.addSparseConstraint('extra1', [2, 7, 12])
const solutions1 = solver1.findAll()

const solver2 = template.createSolver()
solver2.addSparseConstraint('extra2', [3, 8, 13])
const solutions2 = solver2.findAll()

Complex Constraints (Primary + Secondary)

For problems requiring both primary constraints (must be covered exactly once) and secondary constraints (optional - may be left uncovered, but if covered, allow no collisions):

const solver = dlx.createSolver({
  primaryColumns: 2, // First 2 columns are primary
  secondaryColumns: 2 // Next 2 columns are secondary
})

// Method 1: Add constraints separately
solver.addSparseConstraint('constraint1', {
  primary: [0], // Must cover primary column 0
  secondary: [1] // May cover secondary column 1 (optional, but no conflicts if used)
})

// Method 2: Add as binary constraint
solver.addBinaryConstraint('constraint2', {
  primaryRow: [0, 1], // Binary values for primary columns
  secondaryRow: [1, 0] // Binary values for secondary columns
})

Key difference between primary and secondary constraints:

  • Primary: All primary columns MUST be covered exactly once in any valid solution
  • Secondary: Secondary columns are optional - they can be left uncovered, but if a secondary column IS covered, only one constraint can cover it (no collisions allowed)

Solution Methods

// Find one solution
const oneSolution = solver.findOne()

// Find all solutions
const allSolutions = solver.findAll()

// Find up to N solutions
const limitedSolutions = solver.find(10)

Generator Interface (Streaming Solutions)

For large solution spaces or when you need early termination, use the generator interface:

// Stream solutions one at a time
const generator = solver.createGenerator()

let solutionCount = 0
for (const solution of generator) {
  console.log('Found solution:', solution)

  solutionCount++
  if (solutionCount >= 5) {
    // Stop after finding 5 solutions
    break
  }
}

// Manual iteration for full control
const generator2 = solver.createGenerator()
let result = generator2.next()
while (!result.done) {
  processSolution(result.value)
  result = generator2.next()
}

The generator maintains search state between solutions, enabling memory-efficient streaming and early termination without computing all solutions upfront.

Examples

The benchmark directory contains complete implementations for:

  • N-Queens Problem: Classical constraint satisfaction problem
  • Pentomino Tiling: 2D shape placement with rotation constraints
  • Sudoku Solver: Number placement with row/column/box constraints

These examples demonstrate encoding techniques for different problem types and show performance optimization strategies.

Benchmarks

This section contains performance comparisons against other JavaScript Dancing Links libraries, updated automatically during releases.

All benchmarks run on the same machine with identical test cases. Results show operations per second (higher is better).

All solutions to the sudoku

| Library | Ops/Sec | Relative Performance | Margin of Error | | ----------------------- | -------- | -------------------- | --------------- | | dancing-links (sparse) | 12776.77 | 1.00x (fastest) | ±0.16% | | dancing-links (binary) | 5084.72 | 0.40x | ±0.23% | | dance | 2022.24 | 0.16x | ±0.41% | | dancing-links-algorithm | 1397.72 | 0.11x | ±0.38% | | dlxlib | 1274.87 | 0.10x | ±0.71% |

Finding one pentomino tiling on a 6x10 field

| Library | Ops/Sec | Relative Performance | Margin of Error | | ---------------------- | ------- | -------------------- | --------------- | | dancing-links (sparse) | 442.34 | 1.00x (fastest) | ±0.56% | | dancing-links (binary) | 430.33 | 0.97x | ±0.46% | | dlxlib | 348.68 | 0.79x | ±0.68% | | dance | 84.99 | 0.19x | ±0.79% |

Finding ten pentomino tilings on a 6x10 field

| Library | Ops/Sec | Relative Performance | Margin of Error | | ---------------------- | ------- | -------------------- | --------------- | | dancing-links (sparse) | 68.74 | 1.00x (fastest) | ±1.12% | | dlxlib | 68.32 | 0.99x | ±1.11% | | dancing-links (binary) | 67.45 | 0.98x | ±1.14% | | dance | 17.36 | 0.25x | ±0.85% |

Finding one hundred pentomino tilings on a 6x10 field

| Library | Ops/Sec | Relative Performance | Margin of Error | | ---------------------- | ------- | -------------------- | --------------- | | dancing-links (sparse) | 9.11 | 1.00x (fastest) | ±2.07% | | dancing-links (binary) | 8.99 | 0.99x | ±1.67% | | dlxlib | 8.61 | 0.95x | ±2.00% | | dance | 2.37 | 0.26x | ±1.85% |

Testing Environment:

  • Node.js v25.3.0
  • Test cases: Sudoku solving, pentomino tiling (1, 10, 100 solutions)

Last updated: 2026-01-15

Contributing

For development information, performance benchmarking, profiling, and contribution guidelines, see DEVELOPMENT.md.