DEV Community

Mee Mee Alainmar
Mee Mee Alainmar

Posted on

Data Validation Using Early Return in Python

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:

  • age should be at least 18
  • email should contain @
  • user_id should be an integer

The usual way often ends up looking like this:

id="a1k29d"

if "age" in record:
    if record["age"] >= 18:
        ...
Enter fullscreen mode Exit fullscreen mode

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": "@"},
]
Enter fullscreen mode Exit fullscreen mode

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"}
Enter fullscreen mode Exit fullscreen mode

Example

id="d4hf80"
record = {
    "user_id": 1,
    "age": 16,
    "email": "testemail.com"
}

result = validate_record(record, rules)
print(result)
Enter fullscreen mode Exit fullscreen mode

Output:

id="d4hf80"
{
    "status": "FAIL",
    "field": "age",
    "issue": "Must be >= 18"
}
Enter fullscreen mode Exit fullscreen mode

Diagram

 ┌─────────────┐
 │   Function  │
 └──────┬──────┘
        │
        ▼
 ┌─────────────┐
 │ Validate    │
 │ Input       │
 └──────┬──────┘
        │Invalid?
        ├── Yes → Return Error
        │
        ▼
 ┌─────────────┐
 │ Check Pre-  │
 │ conditions  │
 └──────┬──────┘
        │Fail?
        ├── Yes → Return Early
        │
        ▼
 ┌─────────────┐
 │ Main Logic  │
 │ Execution   │
 └──────┬──────┘
        │
        ▼
 ┌─────────────┐
 │ Return      │
 │ Success     │
 └─────────────┘

Enter fullscreen mode Exit fullscreen mode

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)