Introduction

Imagine searching a century-old census for your great-grandmother. You type Kowalski but the clerk wrote Kowalsky. A normal search finds nothing. Soundex finds her.

Soundex is a phonetic algorithm invented by Robert Russell and Margaret Odell around 1918 and adopted for the 1880 and 1900 US Censuses. Its idea is both ancient and elegant: instead of comparing letters, compare sounds. Strip vowels, merge similar consonants, and you get a short code — a phonetic hash — that stays the same no matter how a name is spelled, as long as it sounds the same.

The rules are surprisingly concise. Keep the first letter of the name as-is. Then scan the rest, drop vowels and the letters H, W, and Y (which rarely affect the sound), and replace consonants with digits according to a fixed table: B, F, P, V → 1; C, G, J, K, Q, S, X, Z → 2; D, T → 3; L → 4; M, N → 5; R → 6. Merge consecutive duplicates and pad or trim to exactly four characters total — one letter plus three digits.

The result: Smith → S530, Smyth → S530. Johnson → J525, Jonson → J525. Names that sound alike collide to the same hash; names that sound different stay apart.

Try It: Names That Sound Alike

Type any name below and watch it collapse to a four-character Soundex code. Then try a variant spelling — if the codes match, Soundex would find both in a search.

<!-- Soundex phonetic matching demo — type a name and see its 4-char code -->
<p class="hint">Type any name to see its <b>Soundex code</b> — a 4-character phonetic hash. Names with the same code are phonetic matches, meaning a search for one would find all the others.</p>
<div class="input-row">
  <input id="name-input" type="text" placeholder="Type a name..." autocomplete="off" spellcheck="false" />
  <div class="code-badge" id="code-badge">----</div>
</div>
<div class="steps-box" id="steps-box"></div>
<div class="collisions-section">
  <div class="collisions-label" id="collisions-label">Known names with this code:</div>
  <div class="collisions-list" id="collisions-list"></div>
</div>
<button id="reset-btn" type="button" class="ghost">Reset</button>
/* Main layout styles */
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.hint { font-size: .88rem; color: #444; margin: 0 0 .8rem; line-height: 1.5; }
.input-row { display: flex; align-items: center; gap: .6rem; margin-bottom: .7rem; }
#name-input { flex: 1; font-size: 1.1rem; padding: .4rem .7rem; border: 1.5px solid #cdd9e3;
              border-radius: 8px; outline: none; }
#name-input:focus { border-color: #1d3557; }
.code-badge { font: 700 1.6rem ui-monospace, monospace; letter-spacing: .12em;
              background: #1d3557; color: #fff; border-radius: 10px;
              padding: .25rem .8rem; min-width: 6ch; text-align: center; }
.code-badge.match { background: #0a7d33; }
/* Step-by-step breakdown box */
.steps-box { background: #f0f4f8; border-radius: 8px; padding: .55rem .75rem;
             font-size: .82rem; line-height: 1.6; margin-bottom: .7rem; min-height: 2.4rem; }
.step { margin-bottom: .1rem; }
.step .label { color: #555; }
.step .val { font-family: ui-monospace, monospace; color: #1d3557; font-weight: 600; }
/* Collision chips listing matching names */
.collisions-section { margin-bottom: .7rem; }
.collisions-label { font-size: .82rem; font-weight: 600; color: #555; margin-bottom: .35rem; }
.collisions-list { display: flex; flex-wrap: wrap; gap: .35rem; min-height: 2rem; }
.collision-chip { background: #e8eef3; border: 1px solid #cdd9e3; border-radius: 20px;
                  padding: .18rem .65rem; font-size: .82rem; color: #1d3557; }
.collision-chip.self { background: #1d3557; color: #fff; border-color: #1d3557; }
.collision-chip.empty { color: #888; background: transparent; border: none; font-style: italic; }
button.ghost { font: 600 13px system-ui, sans-serif; padding: .35rem .8rem;
               border: 1px solid #1d3557; background: #fff; color: #1d3557;
               border-radius: 8px; cursor: pointer; }
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Soundex algorithm — returns a 4-char code: 1 letter + 3 digits
function soundex(name) {
  if (!name) return '----';
  // Step 1: keep first letter, strip non-alpha
  const upper = name.toUpperCase().replace(/[^A-Z]/g, '');
  if (!upper) return '----';
  const first = upper[0];
  // Consonant-to-digit mapping table (standard Soundex)
  const MAP = { B:1,F:1,P:1,V:1, C:2,G:2,J:2,K:2,Q:2,S:2,X:2,Z:2,
                D:3,T:3, L:4, M:5,N:5, R:6 };
  // Step 2: encode remaining consonants
  const digits = [];
  let prev = MAP[first] ?? 0;
  for (let i = 1; i < upper.length; i++) {
    const ch = upper[i];
    // H, W, Y are ignored (they do not change the phonetic code)
    if ('HWY'.includes(ch)) continue;
    const code = MAP[ch] ?? 0;
    // Vowels (code 0) reset the dedup guard but are not appended
    if (code === 0) { prev = 0; continue; }
    // Adjacent same-code consonants are merged into one digit
    if (code !== prev) { digits.push(code); prev = code; }
    if (digits.length === 3) break;
  }
  // Pad with zeros to always produce exactly 3 digits after the letter
  while (digits.length < 3) digits.push(0);
  return first + digits.join('');
}

// Built-in list of names used to show phonetic collisions
const NAMES = [
  'Smith','Smyth','Schmidt','Smithe',
  'Johnson','Jonson','Johnsen','Jensen',
  'Robert','Rupert','Roberta',
  'Catherine','Katherine','Kathryn','Katarina',
  'Thompson','Thomson','Tomson',
  'Williams','Williamson',
  'Anderson','Andersen',
  'Jackson','Jaxon','Jessen',
  'Garcia','Garza','Garsia',
  'Rodriguez','Rodrigues',
  'Martinez','Martines',
  'Brown','Braun','Brawn',
  'Taylor','Tailor','Tayler',
  'Wilson','Willson',
  'Lee','Leigh','Lea',
  'Clark','Clarke','Clerk',
  'Walker','Walcker',
  'White','Wight','Whyte',
  'Hall','Haul','Halle',
  'Allen','Alan','Allan'
];

// Render the step-by-step breakdown inside the steps box
function renderSteps(name, code) {
  const box = document.getElementById('steps-box');
  if (!name) { box.innerHTML = ''; return; }
  const upper = name.toUpperCase().replace(/[^A-Z]/g, '') || '?';
  const labels = [
    { label: 'Input:', val: name },
    { label: 'Uppercase, letters only:', val: upper },
    { label: 'Soundex code:', val: code }
  ];
  box.innerHTML = labels.map(l =>
    `<div class="step"><span class="label">${l.label}</span> <span class="val">${l.val}</span></div>`
  ).join('');
}

// Render the list of known names that share the computed code
function renderCollisions(code, inputName) {
  const label = document.getElementById('collisions-label');
  const list = document.getElementById('collisions-list');
  if (!inputName || code === '----') {
    label.textContent = 'Known names with this code:';
    list.innerHTML = '';
    return;
  }
  const matches = NAMES.filter(n => soundex(n) === code);
  label.textContent = i18nFmt('Known names sharing code %code%:', { code: code });
  if (matches.length === 0) {
    list.innerHTML = `<span class="collision-chip empty">No known names share this code.</span>`;
    return;
  }
  const norm = inputName.trim().toLowerCase();
  list.innerHTML = matches.map(n => {
    const isSelf = n.toLowerCase() === norm;
    return `<span class="collision-chip${isSelf ? ' self' : ''}">${n}</span>`;
  }).join('');
}

function update() {
  const raw = document.getElementById('name-input').value;
  const code = soundex(raw.trim());
  const badge = document.getElementById('code-badge');
  badge.textContent = code;
  badge.className = 'code-badge' + (code !== '----' ? ' match' : '');
  renderSteps(raw.trim(), code);
  renderCollisions(code, raw.trim());
}

document.getElementById('name-input').addEventListener('input', update);
document.getElementById('reset-btn').addEventListener('click', () => {
  document.getElementById('name-input').value = '';
  document.getElementById('code-badge').textContent = '----';
  document.getElementById('code-badge').className = 'code-badge';
  document.getElementById('steps-box').innerHTML = '';
  document.getElementById('collisions-label').textContent = 'Known names with this code:';
  document.getElementById('collisions-list').innerHTML = '';
});
// Pre-fill with 'Smith' to show a result on load
document.getElementById('name-input').value = 'Smith';
update();
Result
Console

The demo computes the code in real time and shows you a handful of well-known names that share the same code, so you can see the collision in action. Notice how Robert and Rupert land on the same code, or how Katherine, Kathryn, and Katarina all collapse together — but Catherine gets a different code because Soundex always keeps the first letter.

The Real Complexity

Soundex looks trivially simple — and computationally it is. The algorithm runs in O(n)O(n) time and space in the length of the name, a single left-to-right scan. There is no clever data structure, no recursion, no backtracking.

The hard part is not computation — it is collision design.

  • Over-matching: names that sound quite different can share a code. Jackson (J250) and Jessen (J250) collide, even though no one would confuse them.
  • Under-matching: names that sound nearly identical can differ by code. Lee (L000) and Leigh (L200) do not match because the silent gh adds an R-equivalent code position.
  • Language bias: Soundex was built for English-language transcriptions of mostly European surnames. It performs poorly on names from Arabic, Chinese, Hebrew, or many African languages, where the phonetic rules simply do not apply.

These limitations motivated a chain of successors. Metaphone (Lawrence Philips, 1990) handles more English phonetic rules. Double Metaphone (Philips, 2000) adds a second code for ambiguous pronunciations and covers more language families. NYSIIS (New York State Identification and Intelligence System) targets a different trade-off. Caverphone was designed specifically for New Zealand census names.

Each algorithm is essentially a hand-coded compression of a language's phonology: a function from strings to a smaller alphabet where phonetically close strings hash nearby. None of them solves the general problem — that would require a full model of pronunciation, which brings you into the territory of string algorithms and even speech recognition.

The deeper insight is that Soundex encodes a many-to-one mapping: many strings collapse to one code. This is the same idea as a hash function, but optimized for human sound perception rather than uniform distribution.

Where It Matters

Phonetic matching is one of the oldest and most widely deployed tricks in data management:

  • Genealogy and ancestry search: sites like Ancestry.com and FamilySearch index hundreds of millions of historical records with Soundex so that a spelling variant never hides a relative.
  • Voter registration and deduplication: election authorities use phonetic codes to flag likely duplicate registrations when names are entered with different spellings across records.
  • Medical record linkage: hospitals match patients across admissions even when a name is misspelled, transliterated, or hyphenated differently — a patient safety issue that phonetic hashing partially addresses.
  • Spell-checking and autocorrect: early spell-checkers used phonetic distance to suggest corrections; modern systems layer phonetic similarity on top of edit distance for better suggestions.
  • Search engines and databases: SQL databases expose SOUNDEX() as a built-in function; full-text search engines use phonetic variants as query expansion.

Whenever human names travel through systems — typed by hand, transcribed from speech, translated between alphabets — phonetic matching is the first line of defense against the chaos of spelling variation.

Conclusion

Soundex is over a century old and still embedded in nearly every major database engine. Its power comes not from computational sophistication — the algorithm is four rules and a lookup table — but from a conceptual insight: compress by sound, not by spelling.

That compression is lossy by design. The collisions are a feature, not a bug: you want Smith and Smyth to land in the same bucket. But every lossy compression trades precision for recall, and Soundex makes that trade aggressively. Its successors — Metaphone, Double Metaphone, NYSIIS — all try to find a better point on that curve.

The next time a search engine finds your ancestor under a spelling you never tried, a tiny four-character code from 1918 quietly did its job — reminding us that sometimes the oldest algorithms are the most durable ones.

Share this article

Pick a channel — or use your device's native share sheet.

Comments

Loading comments...

https://www.kipuhub.com/en/article/soundex-phonetic/Content licensed under CC BY-NC 4.0.