HOWTO · Python
Soundex in Python
Implement the Soundex phonetic encoding algorithm in Python to compare English-language names using a four-character code: an initial letter followed by three digits.
On this page
Soundex is a phonetic encoding algorithm for indexing English-language names. It turns a name into a four-character code: the first letter followed by three digits. Names with the same code may sound similar under the algorithm, which can help a search find spelling variants. A matching code is only a candidate match; it does not prove that two names are the same or pronounced identically.
Python does not provide a built-in soundex function. The code below implements the common American Soundex rules so you can see and adapt each step.
What Soundex does
Soundex keeps the first letter of a name and maps later consonants to digits according to broad sound groups. It removes duplicate adjacent codes, then pads a short result with zeroes or truncates a long result to three digits. The initial letter plus those digits form the final code.
For example, the familiar pair Robert and Rupert maps to R163 under these rules. The encoding is deliberately compact: it sacrifices detail to make some names with different spellings land on the same key. It is useful as one search aid, not as a replacement for exact matching or human review.
Implement Soundex in Python
This implementation accepts a string containing at least one ASCII letter. It ignores spaces, punctuation, and other non-ASCII characters, and normalizes letter case. If the input is not a string or contains no ASCII letters, it raises ValueError with a clear message. The code keeps the first letter as the code’s prefix and uses a mapping for the remaining letters.
Run this example:
name = "Robert"
letters = [char.upper() for char in name if ("A" <= char <= "Z") or ("a" <= char <= "z")]
groups = {
**dict.fromkeys("BFPV", "1"),
**dict.fromkeys("CGJKQSXZ", "2"),
**dict.fromkeys("DT", "3"),
"L": "4",
**dict.fromkeys("MN", "5"),
"R": "6",
}
previous = groups.get(letters[0])
digits = []
for letter in letters[1:]:
code = groups.get(letter)
if code is None:
if letter in "AEIOUY":
previous = None
continue
if code != previous:
digits.append(code)
previous = code
print(letters[0] + "".join(digits)[:3].ljust(3, "0"))
R163
The program keeps the first ASCII letter, adds up to three mapped digits, and pads a shorter result with zeroes. The complete function below also checks its input before encoding it.
def soundex(name):
"""Return a four-character Soundex code for an ASCII-letter name."""
if not isinstance(name, str):
raise ValueError("name must be a string")
letters = [
char.upper()
for char in name
if ("A" <= char <= "Z") or ("a" <= char <= "z")
]
if not letters:
raise ValueError("name must contain an ASCII letter")
groups = {
**dict.fromkeys("BFPV", "1"),
**dict.fromkeys("CGJKQSXZ", "2"),
**dict.fromkeys("DT", "3"),
"L": "4",
**dict.fromkeys("MN", "5"),
"R": "6",
}
first = letters[0]
previous = groups.get(first)
digits = []
for letter in letters[1:]:
code = groups.get(letter)
if code is None:
# Vowels and Y separate equal codes; H and W do not.
if letter in "AEIOUY":
previous = None
continue
if code != previous:
digits.append(code)
previous = code
return first + "".join(digits)[:3].ljust(3, "0")
print(soundex("Robert"))
R163
Filter the original characters before uppercasing them. This prevents a Unicode uppercase expansion such as ß becoming ASCII letters; lowercase ASCII letters are still normalized. A non-string input or a string with no ASCII letters raises ValueError.
Understand the Soundex rules
The consonant groups are BFPV → 1, CGJKQSXZ → 2, DT → 3, L → 4, MN → 5, and R → 6. Vowels and Y do not add digits. They separate equal consonant codes, so repeated sounds on either side of one can both contribute. H and W are ignored without separating codes.
The first letter is retained even though it also belongs to a consonant group. To avoid counting it twice, the implementation initializes the previous code from that letter. For example, the initial P and following F in Pfister both map to 1, so only one 1 is emitted for that adjacent group. After processing the name, only the first three digits are kept, and missing positions are filled with zeroes.
Only ASCII letters participate. Spaces and punctuation therefore do not break an adjacent-code sequence in this implementation. This is an explicit normalization choice: if your application needs a different policy for apostrophes, accented letters, or other scripts, define and test that policy before using the code for searches.
Test the implementation
Run the full function with a matching pair and names that exercise the H/W rule:
def soundex(name):
"""Return a four-character Soundex code for an ASCII-letter name."""
if not isinstance(name, str):
raise ValueError("name must be a string")
letters = [
char.upper()
for char in name
if ("A" <= char <= "Z") or ("a" <= char <= "z")
]
if not letters:
raise ValueError("name must contain an ASCII letter")
groups = {
**dict.fromkeys("BFPV", "1"),
**dict.fromkeys("CGJKQSXZ", "2"),
**dict.fromkeys("DT", "3"),
"L": "4",
**dict.fromkeys("MN", "5"),
"R": "6",
}
first = letters[0]
previous = groups.get(first)
digits = []
for letter in letters[1:]:
code = groups.get(letter)
if code is None:
if letter in "AEIOUY":
previous = None
continue
if code != previous:
digits.append(code)
previous = code
return first + "".join(digits)[:3].ljust(3, "0")
for name in ("Robert", "Rupert", "Ashcraft", "Pfister"):
print(f"{name}: {soundex(name)}")
Robert: R163
Rupert: R163
Ashcraft: A261
Pfister: P236
Robert and Rupert share a code. Ashcraft shows that H does not separate repeated consonant codes, while Pfister shows that the initial consonant participates in duplicate suppression across F.
For a project, turn representative names into assertions using the expected codes from the exact Soundex variant you intend to support. Include names beginning with each consonant group, names with vowels between repeated groups, punctuation, and inputs with no ASCII letters. The function’s input checks raise ValueError for invalid input; catch that error at the point where your application can show a useful message or skip the record. Keep these checks close to your own normalization rules, because changing which characters participate can change a code.
Limitations of Soundex
Soundex was designed around English-language pronunciation and spelling conventions. Its broad groups can miss names that people consider similar, and can give the same code to names that are not similar in context. Pronunciation varies across languages, regions, and individuals, while this implementation operates on spelling alone.
The four-character code also creates collisions: many distinct names must share a limited set of possible codes. Use it to narrow a search, then compare fuller records or ask a person to confirm a match. For multilingual data, consider a method designed for the languages and names in your dataset, and document the precise normalization and Soundex variant so results are reproducible.