It’s out there thousands of times. And here it is again. The thing I couldn’t find in my Googling for a good regular expression was the “3 of 4 conditions”. There was “all of 3” or “all of 4”, but I couldn’t find “3 of 4”.
So, here’s what I came up with:
^.*(?=.{10,})((?=.*[a-z])(?=.*[A-Z])(?=.*\d)|(?=.*[a-z])(?=.*[A-Z])(?=.*\W)|(?=.*[a-z])(?=.*\d)(?=.*\W)|(?=.*[A-Z])(?=.*\d)(?=.*\W)).*$
- At least 10 characters
- At least 3 of the following conditions
- Lowercase alpha character
- Uppercase alpha character
- Number
- Non-alphanumeric character
How it works:
Let’s start by looking at the regular expression required for ALL of the 4 conditions listed above:
^.*(?=.{10,})(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*\W).*$
Broken up, the expression says this:
| ^ | The beginning of the text… |
| .* | …has zero or more of any characters… |
| (?=.{10,}) | …where is followed by any character at least 10 times… |
| (?=.*[a-z]) | …and where is followed by any character zero or more times which is followed by a lowercase letter… |
| (?=.*[A-Z]) | …and where is followed by any character zero or more times which is followed by a uppercase letter… |
| (?=.*\d) | …and where is followed by any character zero or more times which is followed by a digit… |
| (?=.*\W) | …and where is followed by any character zero or more times which is followed by a non-word character… |
| .* | …and is followed by any character zero or more times… |
| $ | …then the text ends. |
So, let’s break down the example input: this.IS.a.t34t.
| ^ | Matches the betting of the text. |
| .* | Matches anything zero or more times. (Call this “Match 0”) In this case, it matches the entire text. |
| (?=.{10,}) | Makes “Match 0” match all but the last 10 characters. The expression up to this point is ^.*(?=.{10,}), which matches: this.IS.a.t34t. It matches anything (Match 0) where there are 10 of any character to the right (“to the right” is a zero-width positive lookahead). |
| (?=.*[a-z]) | Makes sure that “Match 0” is followed by anything or nothing, then a single lowercase character. this.IS.a.t34t. “Match 0” is still the red “this”, and the noncapturing lookahead is the blue “a”. |
| (?=.*[A-Z]) | Makes sure that “Match 0” is followed by anything or nothing, then a single uppercase character. this.IS.a.t34t. “Match 0” is still the red “this”, and the noncapturing lookahead is the blue “I”. |
| (?=.*\d) | Makes sure that “Match 0” is followed by anything or nothing, then a single digit. this.IS.a.t34t. “Match 0” is still the red “this”, and the noncapturing lookahead is the blue “3”. |
| (?=.*\W) | Makes sure that “Match 0” is followed by anything or nothing, then a single non-word character. this.IS.a.t34t. “Match 0” is still the red “this” and the noncapturing lookahead is the blue “.”. |
| .* | Now here’s a twist. this.IS.a.t34t. “Match 0” is still the red “this” and the second “anything” match (“Match 1”) is the blue “.IS.a.t34t”. |
| $ | …then the text ends. |
To demonstrate a little bit, let’s name the matches. So our expression becomes:
^(?'match_0'.*)(?=.{10,})(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*\W)(?'match_1'.*)$
Visualizing this:
So you can see that the first match is grouped and named “match_0” and the second match is grouped named “match_1”.
Ok. If you’re still with me, that is how we make it match all of the aforementioned conditions. Now how do we make it match 3 of the 4? There is no simple declarative mechanism for “3 of 4”, so I used a combination.
A combination of 3 of 4 items is only 4, so there’s not that much to do. Remember, we need a combination, in which order is not important. We don’t care about order, just existence. The formula for the number of items in a combination is
(n!)/(r!(n – r)!)
where n is the number of items to choose from (4) and r is the number chosen (3). So (4!) / (3!(4 – 3)!) is 24 / 6 = 4. There are only 4 combinations in a “3 of 4” scenario: (lowercase, uppercase, digit), (lowercase, uppercase, non-alphanumeric), (lowercase, digit, non-alphanumeric), and (uppercase, digit, non-alphanumeric).
As an aside: if the order of the options is important, it is called a permutation. The formula for a permutation is
n! / (n – r)!
which gives us 24 / 1 = 24 permutations. Since we need to use alternation in our regular expression, that’s a lot of typing: (lowercase, uppercase, digit) (lowercase, uppercase, non-alphanumeric) (lowercase, digit, uppercase) (lowercase, digit, non-alphanumeric) (lowercase, non-alphanumeric, uppercase) (lowercase, non-alphanumeric, digit) (uppercase,lowercase, digit) (uppercase,lowercase, non-alphanumeric) (uppercase, digit,lowercase) (uppercase, digit, non-alphanumeric) (uppercase, non-alphanumeric,lowercase) (uppercase, non-alphanumeric, digit) (digit,lowercase, uppercase) (digit,lowercase, non-alphanumeric) (digit, uppercase,lowercase) (digit, uppercase, non-alphanumeric) (digit, non-alphanumeric,lowercase) (digit, non-alphanumeric, uppercase) (non-alphanumeric,lowercase, uppercase) (non-alphanumeric,lowercase, digit) (non-alphanumeric, uppercase,lowercase) (non-alphanumeric, uppercase, digit) (non-alphanumeric, digit,lowercase) (non-alphanumeric, digit, uppercase).
So our expression to require all four conditions is:
^.*(?=.{10,})(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*\W).*$
Which states that each of the lookaheads must be true, (10 chars)(lower)(upper)(digit)(non-alphanumeric). What we want is to make a statement like, “10 characters and at least 3 of my 4 conditions”, which with combinations becomes, “10 characters and at least one of my combinations.” So it would look more like (10 chars)((c1)|(c2)|(c3)|(c4)).
Now replacing with our real regular expression syntax, our final expression is:
^.*(?=.{10,})((?=.*[a-z])(?=.*[A-Z])(?=.*\d)|(?=.*[a-z])(?=.*[A-Z])(?=.*\W)|(?=.*[a-z])(?=.*\d)(?=.*\W)|(?=.*[A-Z])(?=.*\d)(?=.*\W)).*$
With the first combination in red, the second combination in blue, the third combination in orange, and the fourth combination in green.
In my next post, I’ll post my Regular Expression Test Bench. There are a few of these online also, but I needed one with better visualization. I needed to see the matches and captures, and highlight each of them in the text independently on demand. Here is a screenshot of the app. It’s being deployed as a Click-Once app, so I’ll post the URL soon. :)

Development
.net | asp.net