C.W.K.
Stream
Lesson 07 of 10 · published

Password Validation Pattern

~8 min · practical, password, stacked-lookahead

Level 0Pattern-Curious
0 XP0/90 lessons0/15 achievements
0/100 XP to next level100 XP to go0% complete

The canonical multi-rule example

"Password must be 8+ chars, contain at least one lowercase, one uppercase, and one digit." Without lookahead, this is a nightmare: enumerating every order in which the required characters can appear.

With stacked lookaheads, it's clean:

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

Reading left to right:

  • ^ — start of string
  • (?=.*[a-z]) — somewhere ahead is a lowercase letter
  • (?=.*[A-Z]) — somewhere ahead is an uppercase letter
  • (?=.*\d) — somewhere ahead is a digit
  • .{8,} — actually consume 8+ characters
  • $ — end of string

The three lookaheads run AT THE START. Each peeks ahead to verify a constraint exists somewhere in the string. After all three pass, the pattern proceeds to consume 8+ characters.

Why this is the textbook example

It demonstrates everything important about lookaround:

  1. Multiple constraints stacked at the same position.
  2. Each constraint independent (order doesn't matter).
  3. The final consume is separate from the constraints.
  4. The result reads almost like a spec.

Adding more rules

Need at least one special character? Add (?=.*[!@#$%^&*]). Need to forbid spaces? Add (?!.*\s) (negative lookahead). The pattern grows linearly, not exponentially.

Code

Password validators·python
import re

# 8+ chars with lower, upper, digit
weak = re.compile(r'^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$')
bool(weak.match('Aa1aaaaa'))   # True
bool(weak.match('aaaaaaaaa'))  # False (no upper, no digit)
bool(weak.match('Aa1'))        # False (too short)

# Strong: 12+ chars, lower, upper, digit, special, no spaces
strong = re.compile(
    r'^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%^&*])(?!.*\s).{12,}$'
)
bool(strong.match('Pippa@1234abc'))   # True
bool(strong.match('Pippa@1234'))      # False (only 10 chars)
bool(strong.match('Pippa @1234abc'))  # False (has space)

# Composing for readability with VERBOSE
password = re.compile(r'''
    ^                       # start
    (?=.*[a-z])             # at least one lowercase
    (?=.*[A-Z])             # at least one uppercase
    (?=.*\d)                # at least one digit
    (?=.*[!@#$%^&*])        # at least one special
    (?!.*\s)                # no whitespace
    .{12,}                  # 12+ chars total
    $                       # end
''', re.VERBOSE)

External links

Exercise

Build a username validator: 4-20 chars, must start with a letter, must contain at least one digit, must NOT contain hyphens. Use stacked lookaheads. Test against 'pippa1', 'p1ppa', 'pippa', '1pippa', 'pippa-1'.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.