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.
Top comments (0)