2026, Jan 03 01:00

Count Words from a Character Pool Correctly in Python: Use for-else to Avoid Break-Control Bugs

Learn how to fix overcounting when forming words from a character pool in Python. See why break misleads in nested loops and how for-else gives correct counts.

When you need to verify whether words from a list can be assembled from a given pool of characters, a common constraint is that each character in the pool may be used only once. In the example with targets set to ["cat", "bt", "hat", "tree"] and chars set to "atach", the correct count of buildable words is 2. However, a straightforward loop with a break can easily lead to an overcount if the control flow is not handled carefully.

Problem setup

The logic below intends to count how many strings in a list can be formed from a character pool. It tracks character frequencies via Counter and stops scanning a word as soon as a deficit is detected. Despite that, it returns the wrong total.

from collections import Counter

pool = "atach"
items = ["cat", "bt", "hat", "tree"]

# Expectation for this input: 2

def count_buildable(items, pool):
    total_ok = 0
    pool_map = Counter(pool)
    for token in items:
        token_map = Counter(token)
        for ch in token:
            if token_map[ch] > pool_map[ch]:
                break
        total_ok += 1
    return total_ok

For the input above, this version produces 4 instead of 2.

What actually goes wrong

The break only exits the innermost loop. After the inner loop stops, regardless of whether it broke early due to insufficient characters or reached the end successfully, the code increments the counter. As a result, every word is counted, including those that cannot be formed from the pool.

Fixing the control flow

Python’s for-else construct is designed for this scenario. The else clause runs only if the for loop wasn’t terminated by a break. Moving the increment into that else guarantees we count a word only when all its characters pass the frequency check.

from collections import Counter

pool = "atach"
items = ["cat", "bt", "hat", "tree"]

def count_buildable(items, pool):
    total_ok = 0
    pool_map = Counter(pool)
    for token in items:
        token_map = Counter(token)
        for ch in token:
            if token_map[ch] > pool_map[ch]:
                break
        else:
            total_ok += 1
    return total_ok

With the same input, this version yields 2, which aligns with the requirement that each character from the pool can be used only once.

Why this detail matters

In problems that involve early exits from nested loops, it’s easy to unintentionally execute post-loop logic that assumes success. Understanding that break only escapes the innermost loop, and that for-else executes only if no break occurred, helps avoid subtle counting bugs and makes intent explicit.

Takeaways

When validating a sequence under constraints and short-circuiting on failure, place your success action where it runs only if no break happens. The for-else pattern keeps the verification local to the loop, makes the control flow clear, and prevents off-by-many errors like counting every item regardless of eligibility.