← back to writing

The shape of a good tool

Notes on what we look for before we ship a component to the open library: a clear job, a forgiving API, a sensible default, and the willingness to be quietly removed.

What's in this

Every tool we ship from the lab has to earn its place. Not because we’re precious about it — it costs almost nothing to publish a package — but because someone else, somewhere, will read its README at 2am while a sequencer hums in the background, and we owe them a tool that does what it says.

Over the last year we’ve found ourselves applying the same five-question checklist before tagging a release. None of these are clever; all of them are easy to skip.

What’s the one job?

If we can’t say what a tool does in a single sentence, it isn’t ready. Not “what it could be used for” — what it does. The good libraries we use have one-line summaries we can repeat without thinking:

  • Sort tabular data with awareness of column types.
  • Render a sequence with annotations.
  • Spin a molecule.

If your one-liner contains “and” or a comma, you’re shipping two tools.

Will it surprise someone in production?

Lab code has a tendency to get adopted faster than you expect.

By the time anyone notices, it’s running on real samples, and a “neat little helper” has become an npm dependency in someone’s regulated environment.

Before we publish anything, we ask: what’s the worst-case behaviour on bad input? The acceptable answers are throws a clear error and returns a typed empty value. The unacceptable answer is silently mangles.

// Good — the consumer is told their data was the problem
function readSequence(input: string): Sequence {
  if (!input.trim()) {
    throw new SequenceError("Empty sequence");
  }
  // ...
}

// Bad — quietly returns garbage
function readSequence(input: string): Sequence {
  return parseOrFail(input) ?? { bases: [], length: 0 };
}

Is the default the right thing?

A library you have to configure for the common case isn’t doing its job. The default options are the API for 80% of users, and they should produce a reasonable result on a reasonable input without thought.

A bad default is a tax we charge every user for the privilege of running our code.

When in doubt, we look at what the next most-experienced person on the team would expect. Not us, who wrote it; not the most senior, who could reach for an API reference; the person who’s just been onboarded.

Make the easy thing the default. Make the right thing the easy thing.

— A maxim we keep returning to

Can it be deleted without drama?

A tool that’s hard to remove becomes infrastructure. That’s not a compliment — infrastructure tends to outlive its usefulness, accumulate bug reports, and force maintenance work in directions nobody wanted to go.

We aim for tools you can npm uninstall on a Tuesday afternoon and replace with twenty lines of bespoke code if it turns out our abstraction wasn’t the right shape. That means:

  1. No global state. Don’t reach for a singleton just because it’s convenient.
  2. No surprising peer-dependency cascades. If our 8kb library forces you to install 12mb of transitive deps, we’ve made something worse than the problem.
  3. Boring, documented escape hatches. A raw() function that returns the underlying primitive lets people opt out without forking us.

Would we use it on real work tomorrow?

The final test, and the only one that catches the others lying. If we wouldn’t reach for it on a real job under a real deadline — if we’d quietly write a one-off instead — the tool isn’t ready, and pretending otherwise is worse than not publishing.

Most of our seed-status tools fail this question. That’s why they’re seeds. They get to live in the open with rough edges, but we don’t pretend they’re ready.


None of this is novel. It’s the same advice every careful library author has repeated for a decade. We’re writing it down because we keep needing to remind ourselves: a tool isn’t a vehicle for cleverness. It’s a quiet promise to be useful, and to disappear cleanly when it isn’t.

If you have a checklist that catches things ours doesn’t, let us know — we’ll fold it in.