Skip to content

How to Implement Spell Checking with NLTK in Python

Learn to build a custom spell checker from scratch using NLTK's edit distance algorithm, then compare it with production-ready alternatives like pyspellchecker and TextBlob for accuracy and performance.

Spell checking relies on comparing misspelled words against a dictionary and finding the closest matches using string similarity algorithms. This guide covers implementing spell checking from scratch with NLTK and using production-ready alternatives.

Understanding Edit Distance

Edit distance (also called Levenshtein distance) measures the minimum number of single-character operations needed to transform one word into another. These operations include insertions, deletions, and substitutions.

from nltk.metrics.distance import edit_distance

print(edit_distance("happy", "happpy"))  # 1 (delete one 'p')
print(edit_distance("cat", "car"))       # 1 (substitute 't' for 'r')
print(edit_distance("hello", "hallo"))   # 1 (substitute 'e' for 'a')
print(edit_distance("python", "pithon")) # 1 (substitute 'y' for 'i')
print(edit_distance("book", "back"))     # 2 (two substitutions)

Basic Spell Checker with NLTK

Build a simple spell checker using NLTK's word corpus and edit distance:

from nltk.metrics.distance import edit_distance
from nltk.corpus import words


correct_words = words.words()
print(f"Dictionary size: {len(correct_words):,} words")

def suggest_correction(misspelled):
    """Find the closest dictionary word to a misspelled word."""
    misspelled_lower = misspelled.lower()
    
    candidates = [w for w in correct_words 
                  if w[0] == misspelled_lower[0]]
    
    distances = [(edit_distance(misspelled_lower, word), word) 
                 for word in candidates]
    
    return min(distances, key=lambda x: x[0])[1]

print(suggest_correction("happpy"))    # happy
print(suggest_correction("writting"))  # writing
print(suggest_correction("occured"))   # occurred

Enhanced Spell Checker

Improve the basic implementation with multiple suggestions and better handling:

from nltk.metrics.distance import edit_distance
from nltk.corpus import words
from collections import defaultdict

class SpellChecker:
    def __init__(self):
        self.word_set = set(words.words())
        self.word_list = list(self.word_set)
        
        self.by_first_letter = defaultdict(list)
        for word in self.word_list:
            self.by_first_letter[word[0].lower()].append(word.lower())
    
    def is_correct(self, word):
        """Check if a word is spelled correctly."""
        return word.lower() in self.word_set
    
    def suggest(self, misspelled, num_suggestions=5):
        """Return top N spelling suggestions."""
        misspelled_lower = misspelled.lower()
        
        if self.is_correct(misspelled):
            return [misspelled]
        
        first_letter = misspelled_lower[0]
        candidates = self.by_first_letter.get(first_letter, [])
        
        adjacent = self._get_adjacent_letters(first_letter)
        for letter in adjacent:
            candidates.extend(self.by_first_letter.get(letter, []))
        
        scored = [(edit_distance(misspelled_lower, word), word) 
                  for word in candidates]
        
        scored.sort(key=lambda x: x[0])
        return [word for _, word in scored[:num_suggestions]]
    
    def _get_adjacent_letters(self, letter):
        """Get keyboard-adjacent letters for common typo handling."""
        keyboard = {
            'q': 'wa', 'w': 'qeas', 'e': 'wsdr', 'r': 'edft',
            't': 'rfgy', 'y': 'tghu', 'u': 'yhji', 'i': 'ujko',
            'o': 'iklp', 'p': 'ol', 'a': 'qwsz', 's': 'awedxz',
            'd': 'serfcx', 'f': 'drtgvc', 'g': 'ftyhbv', 'h': 'gyujnb',
            'j': 'huikmn', 'k': 'jiolm', 'l': 'kop', 'z': 'asx',
            'x': 'zsdc', 'c': 'xdfv', 'v': 'cfgb', 'b': 'vghn',
            'n': 'bhjm', 'm': 'njk'
        }
        return keyboard.get(letter.lower(), '')

checker = SpellChecker()

print(checker.is_correct("python"))     # True
print(checker.is_correct("pythn"))      # False

print(checker.suggest("happpy"))        # ['happy', 'nappy', ...]
print(checker.suggest("accomodate"))    # ['accommodate', ...]
print(checker.suggest("recieve"))       # ['receive', ...]

Checking Text for Spelling Errors

Process entire sentences or documents:

import re
from nltk.corpus import words

word_set = set(w.lower() for w in words.words())

def find_misspellings(text):
    """Find all misspelled words in text."""
    text_words = re.findall(r'\b[a-zA-Z]+\b', text)
    
    misspelled = []
    for word in text_words:
        if word.lower() not in word_set:
            misspelled.append(word)
    
    return misspelled

text = """
The quikc brown fox jumps over the lazzy dog. 
This sentense contains severl misspeled words.
"""

errors = find_misspellings(text)
print(f"Misspelled words: {errors}")

Using pyspellchecker for Production

For real applications, use the optimized pyspellchecker library:

pip install pyspellchecker
from spellchecker import SpellChecker

spell = SpellChecker()

text = "The quikc brown fox jumps ovr the lazzy dog"
words_list = text.split()

misspelled = spell.unknown(words_list)
print(f"Misspelled: {misspelled}")

for word in misspelled:
    correction = spell.correction(word)
    candidates = spell.candidates(word)
    print(f"{word} -> {correction}")
    print(f"  All candidates: {candidates}")

Output:

quikc -> quick
  All candidates: {'quick', 'quicks'}
ovr -> over
  All candidates: {'over', 'or', 'our'}
lazzy -> lazy
  All candidates: {'lazy', 'jazzy'}

Additional pyspellchecker Features

from spellchecker import SpellChecker

spell = SpellChecker()

print(spell.word_frequency['the'])    # High frequency
print(spell.word_frequency['python']) # Lower frequency

spell.word_frequency.add('pythonic')
spell.word_frequency.add('numpy')

print(spell.unknown(['pythonic', 'numpy']))  # set()

spell.word_frequency.load_words(['tensorflow', 'pytorch', 'sklearn'])

Using TextBlob as an Alternative

TextBlob provides another simple interface for spell checking:

pip install textblob
from textblob import TextBlob

text = TextBlob("I havv a speling eror")
corrected = text.correct()
print(corrected)  # I have a spelling error

word = TextBlob("happpy").words[0]
print(word.spellcheck())

Performance Comparison

import time
from nltk.metrics.distance import edit_distance
from nltk.corpus import words
from spellchecker import SpellChecker

test_word = "accommodatoin"

def nltk_correct(misspelled):
    correct_words = words.words()
    candidates = [w for w in correct_words if w[0] == misspelled[0].lower()]
    distances = [(edit_distance(misspelled.lower(), w), w) for w in candidates]
    return min(distances, key=lambda x: x[0])[1]

spell = SpellChecker()

start = time.time()
for _ in range(10):
    nltk_correct(test_word)
nltk_time = time.time() - start

start = time.time()
for _ in range(100):
    spell.correction(test_word)
pyspell_time = time.time() - start

print(f"NLTK (10 iterations): {nltk_time:.3f}s")
print(f"pyspellchecker (100 iterations): {pyspell_time:.3f}s")

Method Comparison

Method Speed Accuracy Best For
NLTK edit distance 🐢 Slow Good Learning algorithms
pyspellchecker ⚡ Fast Excellent Production applications
TextBlob ⚡ Fast Good Quick prototyping

Summary

Use NLTK's edit distance when you want to understand how spell checking algorithms work or need fine-grained control over the matching process. For production applications, switch to pyspellchecker: it is faster, uses word frequency for better suggestions, and supports custom dictionaries out of the box.

Navigation