While working with data, I find validation logic tends to get messy faster than expected.
It usually starts simple then a few more checks get added, and suddenly everything is wrapped in nested if statements.
That pattern works, but it doesnβt feel great to read or maintain.
That's how I learned Early return (or guard clause) pattern.
Note: In programming, return means sending a value back from a function to wherever that function was called and stopping the functionβs execution right there. Meaning return acts as a checkpoint.
Think of it like saying, βIβm done; hereβs my answer.β
So I tried a different approach, combining:
- early return
- rules defined as simple dictionaries
The result turned out surprisingly clean.
The Problem
Let's say a validation task might involve checks like:
-
ageshould be at least 18 -
emailshould contain@ -
user_idshould be an integer
The usual way often ends up looking like this:
id="a1k29d"
if "age" in record:
if record["age"] >= 18:
...
It works, but the structure quickly becomes hard to follow as more rules are added.
The Pattern
Instead of hardcoding each condition, the rules can be defined as data:
id="ab123"
rules = [
{"field": "user_id", "type": "type", "value": int},
{"field": "age", "type": "min", "value": 18},
{"field": "email", "type": "contains", "value": "@"},
]
Then a single function applies these rules.
id="ab123"
def validate_record(record: dict, rules: list) -> dict:
for rule in rules:
field = rule["field"]
# early return: missing field
if field not in record:
return {
"status": "ERROR",
"field": field,
"issue": "Missing field"
}
value = record[field]
# type check
if rule["type"] == "type":
if not isinstance(value, rule["value"]):
return {
"status": "FAIL",
"field": field,
"issue": f"Expected {rule['value'].__name__}"
}
# minimum value
elif rule["type"] == "min":
if value < rule["value"]:
return {
"status": "FAIL",
"field": field,
"issue": f"Must be >= {rule['value']}"
}
# contains (for strings)
elif rule["type"] == "contains":
if rule["value"] not in value:
return {
"status": "FAIL",
"field": field,
"issue": f"Must contain '{rule['value']}'"
}
return {"status": "OK"}
Example
id="d4hf80"
record = {
"user_id": 1,
"age": 16,
"email": "testemail.com"
}
result = validate_record(record, rules)
print(result)
Output:
id="d4hf80"
{
"status": "FAIL",
"field": "age",
"issue": "Must be >= 18"
}
Diagram
βββββββββββββββ
β Function β
ββββββββ¬βββββββ
β
βΌ
βββββββββββββββ
β Validate β
β Input β
ββββββββ¬βββββββ
βInvalid?
βββ Yes β Return Error
β
βΌ
βββββββββββββββ
β Check Pre- β
β conditions β
ββββββββ¬βββββββ
βFail?
βββ Yes β Return Early
β
βΌ
βββββββββββββββ
β Main Logic β
β Execution β
ββββββββ¬βββββββ
β
βΌ
βββββββββββββββ
β Return β
β Success β
βββββββββββββββ
What is better about this
You can see a few things stood out after using this pattern:
- The logic stays flat, no deep nesting
- Rules are easy to scan and update
- Adding a new validation doesnβt require touching the core function
- Early return keeps the flow straightforward
It feels closer to describing what to validate instead of how to validate it step by step.
This example shows the pattern scales nicely. Running this pattern across a dataset and turning the results into a table would be a natural next step. In a way, it feels like a tiny version of larger data validation tools, just stripped down to the core idea.
For Schema Validation, Pydantic is the best no doubt for this. It ensures that the data entering the system is the right shape, type, and format. Meanwhile, Early Return pattern is to handle edge cases or invalid states immediately, preventing deeply nested if/else blocks.












