Password Strength Regex with Lookaheads
See how lookaheads check several rules at once (12 or more characters with a lower-case letter, a capital, a digit and a symbol) and why current guidance prefers length to rules like these.
One case per line; the m flag makes ^ and the end anchor work line by line.
Link optionspattern only
The address bar holds the pattern, flags and replacement, so Copy link shares them. Your test text stays out of it unless you include it (up to 2,000 characters), because it may be private. Nothing is sent to a server.
Matches
5matches
The first is “Correct-Horse-9-Battery”, at position 0.
- Groups
- none
- Characters matched
- 81 of 186
What the pattern meansHover or tap a part to see it in the pattern and what it matched
How this works: Method, 5 sources, Checked against 1 worked example,
How this works
Method
Your pattern runs in your own browser’s JavaScript engine, in a background worker that is stopped after 1.5 seconds, so a pattern that backtracks catastrophically can’t freeze the page. The explanation comes from our own parser of the ECMAScript pattern grammar, checked against the engine; what each part matched is found by wrapping that part in one more group and running the pattern again. Conversions to other flavours only rewrite the syntax and list what their documentation says works differently.
Sources
How it’s tested
One worked example for this page is checked by automated tests before every release: given the inputs, the tool must show the expected answer.
Changes
- First published, with a library of 19 common patterns and their test cases.
Worked example
Take Correct-Horse-9-Battery, the first case in the tester. Reading the pattern left to right, each part takes its share of the text:
^The start of the text (matches a position, no characters)(?=.*[a-z])Followed by (checked, not part of the match): (matches a position, no characters)(?=.*[A-Z])Followed by (checked, not part of the match): (matches a position, no characters)(?=.*\d)Followed by (checked, not part of the match): (matches a position, no characters)(?=.*[^A-Za-z0-9])Followed by (checked, not part of the match): (matches a position, no characters).{12,}12 or more characters other than line breaks matches Correct-Horse-9-Battery$The end of the text (matches a position, no characters)
How the lookahead password regex works
A lookahead \(?=…)\ checks that something follows the current position without moving past it. Because all four lookaheads sit right after the caret, each one scans the whole password from the start, and the regex only goes on if every one succeeds. \(?=.*[a-z])\ means "somewhere ahead there is a lower-case letter"; the others ask for a capital, a digit (\\d\) and a character that is neither a letter nor a digit (\[^A-Za-z0-9]\).
Only after the checks does the pattern consume anything: \.{12,}\ takes twelve or more characters up to the end. That split, zero-width checks first and the real match last, is the technique worth learning, because it lets you add or drop a rule without rewriting the rest. To demand two digits, for instance, use \(?=(?:.*\d){2})\.
Should you use it for real sign-ups? NIST's current digital identity guidance says no to composition rules: services shall not require mixtures of character types, should accept at least 64 characters, and should instead reject passwords found in lists of breached and common passwords. Rules like these push people towards "Password1!"-style variations while rejecting long passphrases. If you need a regex at all, a length check such as \^.{15,64}\ followed by the end anchor, plus a breached-password lookup, follows that guidance better.
- NIST SP 800-63B says verifiers shall not impose composition rules such as requiring mixtures of character types, and should permit passwords of at least 64 characters. Source: NIST SP 800-63B-4, section 3.1.1.2.
- The same guidance requires at least 15 characters for a password used on its own, and checking new passwords against a blocklist of common and compromised ones. Source: NIST SP 800-63B-4, section 3.1.1.2.
- A lookahead tries to match what follows without consuming any input, so the position stays where it was. Source: MDN: Lookahead assertion.
Test cases
Every case runs as an automated test of this page’s pattern, so the table can’t drift from what the pattern really does.
| Text | Result | Why |
|---|---|---|
| Correct-Horse-9-Battery | Passes | all four kinds, 23 characters |
| Sunset!Over2Lakes | Passes | symbol, digit, both cases |
| pA55word#2026x | Passes | meets every rule, yet it’s a guessable variation |
| Blue_Kettle_88 | Passes | the underscore counts as a symbol |
| Ünïcode-Pass1 | Passes | accented letters count as symbols here |
| correct horse battery staple | Fails | a long passphrase NIST would accept; this rule rejects it |
| Short1! | Fails | only 7 characters |
| alllowercase123! | Fails | no capital letter |
| ALLUPPERCASE123! | Fails | no lower-case letter |
| NoDigitsHere!! | Fails | no digit |
| NoSymbols12345 | Fails | no symbol |
What it doesn’t check
- Meeting every rule doesn’t make a password strong: pA55word#2026x passes and is still easy to guess.
- The dot doesn’t match line breaks, so a password containing a newline fails; that is usually what you want.
- \
[^A-Za-z0-9]\counts accented letters and spaces as symbols; with the u flag you could use \\p{L}\and \\p{N}\to classify letters in every script. - Without the u flag the length counts UTF-16 code units, so an emoji counts as two characters.
- Never send the password anywhere to check it; run the regex in the browser and on your server only.
The same pattern in other languages
Converted automatically from the JavaScript version; the tester above always runs JavaScript.
| Flavour | Pattern | Notes |
|---|---|---|
| Python | (?a)^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^A-Za-z0-9]).{12,}\Z | |
| PCRE | ^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^A-Za-z0-9]).{12,}\z | |
| Go | not possible | Go’s RE2 has no lookahead or lookbehind: it guarantees linear time instead. Match more and check the rest in code. |
| Java | ^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^A-Za-z0-9]).{12,}\z | |
| .NET | ^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^A-Za-z0-9]).{12,}\z | In .NET, \d and \w match any Unicode digit or letter. Pass RegexOptions.ECMAScript, or write [0-9] and [A-Za-z0-9_], for JavaScript’s ASCII-only meaning. |
Sources
Frequently Asked Questions
What is a good regex for a strong password?
The lookahead pattern here checks length and character types, and it is a good way to learn lookaheads. For real accounts, current NIST guidance prefers a minimum length and a check against breached passwords over character-type rules.
How do lookaheads check several rules at once?
Each lookahead starts at the beginning of the password and only looks; it doesn’t move the position. So four lookaheads in a row are four independent checks, and the match continues only if all of them pass.
Why does a long passphrase fail this regex?
It has no capital, digit or symbol, so three lookaheads fail even though it is long and hard to guess. That mismatch is the main argument against composition rules.
Should I tell users which rule they missed?
Yes. Test each rule separately in code and show the ones that fail, instead of one regex that only says no.