PatternRegex

Enforce a strong password policy

One lookahead per rule: a lowercase, an uppercase, a digit and a symbol, minimum 8 characters — and why lookaheads are the right tool for the job.

Last updated

Pattern
(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^\w\s]).{8,}
Open in Regex Tester & Builder

Matches

  • Tr0ub4dor&3
  • correct-Horse-7!

Doesn’t match

  • alllowercase1!
  • NOLOWER1!
  • Short1!

Why it’s written this way

Each (?=…) is a lookahead: it scans ahead for one required character class without consuming anything, then hands control back to the start. Four independent rules become four independent lookaheads — add or drop a rule without rewriting the rest.

Only the final .{8,} actually consumes characters, which is where the length rule lives. When validating a whole input field, anchor it as ^…$ so a strong substring inside a weak password cannot pass.

Edge cases to know

  • →The symbol rule [^\w\s] means "not alphanumeric, not whitespace" — spaces inside a passphrase pass the length rule but do not count as the symbol.
  • →Composition rules are a floor, not a strength meter: Password1! passes. Pair it with a length slider or a breach check for real strength.
  • →NIST guidance now favours length over forced composition — this pattern is for when policy demands composition anyway.

Part by part

(?=.*[a-z])
A lookahead: from the start, somewhere ahead there is a lowercase letter. It checks without consuming, which is what lets four separate requirements apply to the same string.
(?=.*[A-Z])
The same test for an uppercase letter. Order between the lookaheads is irrelevant — each scans the whole string independently.
(?=.*\d)
Requires at least one digit anywhere in the password.
(?=.*[^\w\s])
Requires a symbol, defined as anything that is neither a word character nor whitespace. Written as an exclusion so it covers punctuation the author never thought to list.
.{8,}
The only part that consumes anything: eight or more characters. Every lookahead above has already passed by the time this runs.

Common mistakes

  • [a-z][A-Z]\d[^\w\s].{4,}

    Listing the classes in sequence demands them in that exact order, so it accepts aA1!xxxx and rejects almost every password a person would actually choose. Lookaheads exist precisely to avoid ordering the requirements.

    (?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^\w\s]).{8,}
  • (?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}

    Dropping the symbol lookahead while keeping the messaging that promises a symbol requirement is worse than either choice alone: the form tells users a rule that it does not enforce.

  • (?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^\w\s]).{8,20}

    An upper length bound breaks password managers and passphrases, and a maximum length is a strong hint that the password is being stored somewhere it should not be. Hashes are fixed-length regardless of input.

Questions

Does it allow spaces in a password?

Yes. The dot matches a space, so a passphrase counts toward the length requirement — but whitespace will not satisfy the symbol rule, because that class excludes it explicitly. A four-word passphrase still needs one punctuation mark.

Should I require character classes at all?

Current NIST guidance says no: composition rules push people toward predictable substitutions like P@ssw0rd1 while length is what actually resists cracking. If you can choose, a long minimum with a breached-password check beats this pattern outright.

Does the dot match every character?

Not newlines, unless the s flag is set. It rarely matters for a password field, but it does mean a multi-line paste will not match, which can look like an arbitrary rejection to whoever pasted it.

Is this enough on its own?

No. It cannot tell that a password appears in a breach list, matches the user's email, or is the same one they used last time. Those checks catch far more weak passwords than any composition rule does.

Related in Patterns