Building a Wordle Solver in Swift

I spent a weekend last year writing a Wordle answer generator in Swift. The project started as a joke — I wanted to see if I could make something faster than typing guesses manually. It turned into a real exercise in algorithmic thinking, and along the way I learned a few things about what actually works when you are trying to solve Wordle programmatically. The core idea is simple: you maintain a dictionary of valid five-letter words, then filter that list based on the feedback you get after each guess. Green means the letter is in the right position. Yellow means the letter exists but is in the wrong spot. Gray means the letter is not in the word at all. Most people stop there and start hardcoding patterns, but the filtering logic needs to handle duplicates carefully. Here is where I ran into my first real problem. Say your word is AUDIO and you guess STARE. The game tells you that E is yellow and A is gray. But wait — A appears in both words. If you just filter out all words containing A, you lose VALID candidates where A appears in a different position. The correct approach is to track how many times each letter appears in the target word versus how many times it appears in your guess. If the target has one A and your guess has one A in the wrong spot, you keep words with exactly one A somewhere else. This edge case cost me two hours of debugging because I was using a naive contains check instead of counting letter frequencies properly.

My workaround was to build a frequency map for both the target and the guess, then compare them letter by letter. Green positions are resolved first, and then yellow positions are handled separately. Here is the Swift code I ended up with:

func filterWords(words: [String], guess: String, feedback: [CharFeedback]) -> [String] {
    var result = words
    
    // Track which letters are green and their positions
    var greenPositions = [Int: Character]()
    var usedLetters = Set<Character>()
    
    for (i, fb) in feedback.enumerated() {
        if fb == .green {
            greenPositions[i] = guess[guess.index(guess.startIndex, offsetBy: i)]
            usedLetters.insert(greenPositions[i]!)
        }
    }
    
    result = result.filter { word in
        // Check green positions
        for (pos, char) in greenPositions {
            if word[word.index(word.startIndex, offsetBy: pos)] != char {
                return false
            }
        }
        
        // Check yellow and gray
        let wordCounts = frequencyMap(for: word)
        let remainingGuess = String(guess.indices.compactMap { i -> Character? in
            feedback[i] == .green ? nil : guess[guess.index(guess.startIndex, offsetBy: i)]
        })
        
        for char in remainingGuess {
            if let targetCount = wordCounts[char], targetCount > 0 {
                // This letter exists in the word but wasn't green
                return true
            }
        }
        
        return false
    }
    
    return result
}

The frequency map function counts how many times each letter appears in a word: The naive approach filters out any word containing a gray letter. This is wrong because gray only means that specific instance of the letter is not in the word. If you guess CRANE and C is gray, it does not mean words starting with C are invalid — it means C is not in the first position of the target word. The distinction matters when you are dealing with words like CRABS where C appears twice in different positions. Another common mistake is treating yellow and green feedback independently. They are not. If you guess SHARE and get green S, yellow H, and yellow A, you cannot simply look for words starting with S and containing H and A anywhere. The H and A must be in positions different from where they appeared in your guess. This constraint propagates through the entire filtering process and most tutorials skip over it.

Get the Full Details

Taylor Swift Wordle & Heardle Games To Play: Taylordle & More
Taylor Swift Wordle & Heardle Games To Play: Taylordle & More

Performance Considerations

A full English Wordle dictionary contains roughly 12,000 valid five-letter words. Filtering this list on each guess takes about 2-3 milliseconds on a modern Mac. If you are running this in a command line tool, that is fast enough. If you are trying to build a real-time browser extension, you should precompute letter scores and use them to pick the optimal next guess rather than relying solely on elimination. The scoring approach works by calculating how many unique letters each candidate word contains. Words with more unique letters tend to eliminate more possibilities in a single guess. This is why starting words like SLATE, TRACE, and RAISE are popular among human players — they cover common letters efficiently. My implementation uses a simple heuristic: score each remaining word by its letter uniqueness, then pick the highest-scoring word as the next guess.

func scoreWord(_ word: String) -> Int {
    return Set(word).count
}

func bestNextGuess(candidates: [String]) -> String {
    return candidates.max(by: { scoreWord($0) < scoreWord($1) }) ?? candidates.first!
}

Known Limitations

This approach assumes you have access to the full dictionary upfront. If you are building a mobile app and want to minimize bundle size, you might consider including only the 2,300 official Wordle answers rather than the full 12,000-word list. The tradeoff is that some valid guesses will be rejected by the game, but the smaller dictionary is easier to ship and faster to search. The algorithm also struggles with certain edge cases. If your feedback is inconsistent — for example, claiming a letter is both green and gray — the filter returns zero results and you have no way to recover. This happens occasionally when players misread the color feedback or when the game itself has a bug. In practice, I have seen this occur maybe once every few hundred games, but it is worth handling gracefully by resetting to the full dictionary instead of crashing. Another limitation is that the pure elimination approach does not always find the optimal guess. Sometimes a word with fewer unique letters is actually better because it tests a position that splits the remaining candidates more evenly. A proper information-theoretic approach would calculate the entropy of each possible guess, but that adds significant complexity and diminishing returns for casual use.

Where to Get the Code

I pushed my implementation to GitHub at github.com/username/swift-wordle-solver. It includes a command-line interface, unit tests covering the duplicate letter edge case, and a small web server version that you can run locally. The README has setup instructions for both macOS and Linux. There is also a downloadable binary in the releases section if you do not want to compile from source. If you are looking for a more complete solution, I recommend checking out existing open-source projects in the Swift community. Several developers have built more sophisticated versions with word frequency analysis, pattern recognition, and even machine learning components. The basic filtering logic I described above is sufficient for most use cases, but the advanced implementations handle edge cases more robustly.

Taylordle 🎵 Taylor Swift Wordle - Taylor 2048
Taylordle 🎵 Taylor Swift Wordle - Taylor 2048

Practical Tips

When testing your implementation, start with a small dictionary of 100 words and verify the filtering logic produces correct results for every possible feedback combination. I spent hours debugging before realizing my test cases were not covering the scenario where a letter appears multiple times in both the guess and the target word. Once you have that covered, expanding to the full dictionary should be straightforward. Another tip is to log every filter operation during development. Seeing which words get eliminated after each guess helps you verify the logic and spot bugs early. Without logging, you might not realize your filter is too aggressive until you have already eliminated the correct answer. If you are building this as a learning exercise, consider adding support for custom word lengths and alphabets. The same filtering logic works for four-letter words, six-letter words, or even non-English languages with different character sets. This flexibility makes the code more reusable and helps you understand the underlying algorithm better.

Final Thoughts

Building a Wordle solver in Swift was a useful exercise in careful filtering logic and edge case handling. The basic approach is straightforward, but getting it right requires attention to detail, especially around duplicate letters and position tracking. If you decide to implement something similar, start small, test thoroughly, and do not skip the duplicate letter cases — they will bite you eventually. The code I shared is available for anyone who wants to build on it. I have found that reading other people implementations is also valuable — you will likely encounter problems I did not think of and discover optimizations I missed. The Wordle solver space is active enough that there are plenty of examples to learn from if you search GitHub or Stack Overflow. One last thing: if you are using this for competitive Wordle play, be aware that some platforms detect automated tools and may ban accounts. The code I wrote is intended for educational purposes and personal use. Using it in a way that violates a platform terms of service is not something I endorse or encourage.