Technology Apr 18, 2026 · 3 min read

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...

DE
DEV Community
by Mee Mee Alainmar
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:
        ...

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.

DE
Source

This article was originally published by DEV Community and written by Mee Mee Alainmar.

Read original article on DEV Community
Back to Discover

Reading List