Skip to main content

Real-World RegEx Validations: Production Patterns

In real software systems (like banking apps, sign-up forms, and payment checkouts), you cannot afford bad data crashing your backend database.

Here are the four most common, battle-tested regular expression validation patterns used in corporate Python engineering.


1. Validating Email Addresses

A robust email validator checks for username characters, an @ symbol, a valid domain name, and a top-level domain (.com, .in, .org, etc.):

import re

def is_valid_email(email_str):
# Pattern: characters @ domain . extension
pattern = r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$"
return bool(re.fullmatch(pattern, email_str.strip()))

# Tests
print(is_valid_email("support@thinkittelugu.in")) # Output: True
print(is_valid_email("invalid-email@com")) # Output: False
print(is_valid_email("sai..kumar@gmail.com")) # Output: True

2. Validating Indian Mobile Numbers

In India, mobile numbers:

  • Start with 6, 7, 8, or 9
  • Contain exactly 10 digits
  • May optionally have a +91 or 0 prefix with optional spaces
import re

def is_valid_indian_phone(phone_str):
# Pattern allows optional +91 or 0 prefix followed by 10 digits starting 6-9
pattern = r"^(?:\+91[\s-]?)?[6-9]\d{9}$"
return bool(re.fullmatch(pattern, phone_str.strip()))

print(is_valid_indian_phone("+91 9876543210")) # Output: True
print(is_valid_indian_phone("9876543210")) # Output: True
print(is_valid_indian_phone("1234567890")) # Output: False (starts with 1)

3. Validating Indian PAN Cards

Indian PAN numbers have a strict government format:

  • Exactly 5 uppercase letters (e.g. ABCDE)
  • 4 numeric digits (e.g. 1234)
  • 1 uppercase letter at the end (e.g. F)
import re

def is_valid_pan(pan_number):
pattern = r"^[A-Z]{5}[0-9]{4}[A-Z]{1}$"
return bool(re.fullmatch(pattern, pan_number.upper().strip()))

print(is_valid_pan("ABCDE1234F")) # Output: True
print(is_valid_pan("12345ABCDE")) # Output: False

4. Greedy vs. Non-Greedy (Lazy) Matching

By default, the quantifiers *, +, and ? are greedy — they eat as much text as possible.

To make a quantifier lazy (stop at the earliest possible match), add a ? after it:

import re

html_tag = "<div>Hello</div><div>World</div>"

# ❌ Greedy matching (*): Consumes from first <div> all the way to last </div>
greedy_result = re.findall(r"<div>.*</div>", html_tag)
print("Greedy:", greedy_result)
# Output: Greedy: ['<div>Hello</div><div>World</div>']

# ✅ Lazy matching (*?): Stops at the very first closing </div>
lazy_result = re.findall(r"<div>.*?</div>", html_tag)
print("Lazy:", lazy_result)
# Output: Lazy: ['<div>Hello</div>', '<div>World</div>']

Quick Summary

  • Exact Boundary Validation: Combine ^ (start) and $ (end) to validate entire input fields (e.g., r"^[6-9]\d{9}$").
  • Quantifiers: + (1 or more), * (0 or more), ? (0 or 1), {min,max} (exact range).
  • Greedy vs. Lazy: Standard quantifiers consume maximum matching characters; append ? (*?, +?) to make them match lazily.

What's Next?

Now let's explore handling timestamps, durations, timezones, and mathematical operations in Module 20: Date, Time & Math!