UTXO Coin Selection in Production

Maciej Lewandowski · July 2026 · Field notes from custody & wallet infrastructure

On account-based chains a balance is a number. On Bitcoin it is a set of unspent outputs, and every withdrawal is a small optimization problem: which coins do you spend, what does it cost, what does it leak, and what mess does it leave for the next withdrawal. Coin selection is where those trade-offs stop being theoretical.

The problem nobody states out loud

A withdrawal of 0.5 BTC is not "subtract 0.5 from balance". It is: pick a subset of your UTXOs summing to at least 0.5 plus fees, where the fee depends on how many inputs you picked (each input adds bytes, each byte costs the current feerate), produce a change output for the remainder, and accept that your choice just changed the shape of your wallet for every future withdrawal. Greedy answers to this create three distinct production problems: fees, dust, and privacy.

Fees: inputs are the expensive part

Dust: small enough to ignore, sharp enough to cut

Dust is any output too small to be worth spending. It arrives two ways: as change you created yourself, and as deposits you did not ask for. Both need policy:

Privacy: every selection is a statement

Spending UTXOs together links them publicly and forever: the common-input-ownership heuristic is the bread and butter of chain analytics. For an institution this is not about hiding, it is about not broadcasting your entire operational graph:

The part tutorials skip: concurrency

Real custody systems process withdrawals in parallel, and a UTXO can fund exactly one transaction. Without reservation, two concurrent withdrawals select overlapping inputs and one becomes an accidental double-spend attempt: rejected by the network, flagged by monitoring, alarming in an audit trail even when harmless.

Treat UTXOs like inventory: selection places a hold, broadcast consumes it, failure releases it, and crash recovery must resolve in-flight holds by checking what actually reached the chain. If the words "hold", "release" and "crash recovery" are missing from a wallet design, coin selection is not finished.

Two operational corollaries. First, reserve fee-bumping capacity: if every confirmed UTXO is committed to pending withdrawals, there is nothing left to CPFP a stuck transaction with, exactly when it is needed. Second, batching (one transaction, many withdrawal outputs) is a huge fee win, but it couples customers: one stuck batch is forty stuck withdrawals with one shared fee-bump decision. Batch, but with a bounded blast radius.

Production rules that held up

  1. Balance is a set. Report spendable balance from selectable UTXOs, not from the raw sum; the difference (dust, unconfirmed, reserved) is real.
  2. Selection is fee-aware: it knows the feerate before choosing, not after.
  3. Change below dust threshold becomes fee, with a ledger entry explaining it.
  4. Fresh change address every time, from HD derivation.
  5. Incoming dust is quarantined, excluded from selection by default.
  6. Consolidation is scheduled maintenance in low-fee windows, with the privacy linkage accepted explicitly.
  7. UTXO reservation with holds, releases and crash recovery. Tested, not assumed.
  8. Keep spare UTXOs for CPFP; never commit the whole wallet to in-flight spends.
  9. Batch withdrawals with a bounded blast radius.

Coin selection looks like an algorithm question. In production it is an inventory management problem, a cost problem, a privacy problem and a concurrency problem wearing the same trench coat. Design for all four.