Useful Feedback
A score tells someone where they are. It doesn't tell them what to do next. "Your password is weak (2/5)" is the digital equivalent of a shrug. The useful version is: "Make it longer. Add an uppercase letter. Add a symbol." Three concrete actions, and the user is unstuck.
Good news - we already did the hard part. Each failed rule maps to exactly one fix. The length rule failed? "Make it longer." The symbol rule failed? "Add a symbol." This phase is mostly bookkeeping: pair every rule with the sentence the user should see when that rule fails, then collect the sentences for the rules that didn't pass.
Pairing rules with advice
The clean way to do this is a list of (rule_function, message) pairs. We walk the list, run each rule, and whenever a rule returns False we keep its message. The result is a list of fixes - empty if the password passed everything.
return >=
return
return
return
return
=
return
# missing lower, digit, symbol
# passes everything -> []
Read the feedback function once and the whole design clicks. It's a list comprehension that keeps a message only when its rule fails (if not rule(password)). One pass, no flags, no nested if. Add a rule to the checks list and its advice shows up automatically - same pattern as the score in the last phase, which is the point: the rules are the single source of truth and everything hangs off them.
When there's nothing to fix
Notice feedback("Password1!") returns an empty list. That's not a bug, it's information: an empty list means "nothing to fix". When we print results we'll translate that into a friendly "Looks good!" rather than showing the user a blank space. Handling the empty case on purpose is the difference between code that feels finished and code that feels half-done.
Feedback for every sample
Let's run it over a spread of passwords and print each one's fixes as a tidy list. This is what you'd render under a password field as the user types.
return >=
return
return
return
return
=
return
=
=
Run it. Each password gets a little checklist. "cat" gets a stack of fixes; "correct horse battery staple" gets "Looks good!" because a long lowercase passphrase clears the length floor and the lowercase class - which, honestly, matters more than scattering symbols around. The feedback is now something a real human can act on in five seconds.
Folding score, label, and feedback together
We now have all three outputs - score, label, feedback. Here's a preview of the combined result, the shape we'll finish in the last phase. We return a dictionary so a caller (a web form, an API) can pick out whatever piece it needs.
return >=
return
return
return
return
=
return
return
return
=
=
=
return
We pulled the rule list out to a module-level RULES so both the score and the feedback read from the same source - no chance of them disagreeing. We run each rule once into passed, then reuse that list for both the count and the messages. Running the rules twice would be wasteful and, worse, a place for the score and the advice to drift apart.
Try it yourself
- Reword a message. Change "Add a number" to "Throw in a digit or two". The fix flows straight to the output - copy lives in one place.
- Add a password with a leading space like
" hunter2". It passeshas_symbol(the space) - a reminder that our symbol rule is permissive on purpose.
There's still a hole, and it's a big one. "P@ssw0rd!" scores 5 and gets "Looks good!" - but it's one of the most-guessed passwords on earth. No rule we've written can catch it, because it genuinely passes all of them. The fix isn't another rule; it's a blocklist. That's the final phase.