Python
function-exit
early-return
coding-best-practices
programming

What is the best way to exit a function which has no return value in python before the function ends e.g. a check fails?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

In Python, the clean way to stop a function early is return, even when the function has no explicit return value. Early exit keeps control flow simple and avoids deeply nested conditionals. Combined with guard clauses, this style makes validation logic readable and easy to maintain.

Use Guard Clauses with return

A guard clause checks a precondition and exits immediately if it fails. This prevents unnecessary work and keeps main logic at the top indentation level.

python
1def process_order(order):
2    if order is None:
3        return
4
5    if "items" not in order or not order["items"]:
6        return
7
8    total = sum(item["price"] for item in order["items"])
9    print(f"processing order total={total}")
10
11process_order(None)
12process_order({"items": []})
13process_order({"items": [{"price": 10}, {"price": 15}]})

This is idiomatic and immediately clear to other Python developers.

Return Values and Type Hints

If a function is procedural and returns no meaningful value, annotate with -> None. You can still use plain return for early exits.

python
1def send_email(recipient: str, body: str) -> None:
2    if not recipient:
3        return
4    if "@" not in recipient:
5        return
6    print(f"sending email to {recipient}: {body}")

This pattern communicates intent and avoids ambiguity about expected outputs.

Early Exit Versus Exceptions

Use early return when failed checks are expected and part of normal flow, such as empty input or feature flags. Use exceptions when the condition is exceptional and callers should handle failure explicitly.

python
1def parse_port(value: str) -> int:
2    if not value.isdigit():
3        raise ValueError("port must be numeric")
4    port = int(value)
5    if port <= 0 or port > 65535:
6        raise ValueError("port out of valid range")
7    return port
8
9
10def maybe_run(enabled: bool) -> None:
11    if not enabled:
12        return
13    print("job executed")

This distinction improves API design and logging quality.

Refactoring Long Functions

If you have many early checks, split the function into validation and execution helpers. This avoids a long list of guards and makes unit tests focused.

python
1def validate_payload(payload: dict) -> bool:
2    required = ["user_id", "event", "timestamp"]
3    return all(k in payload for k in required)
4
5
6def handle_event(payload: dict) -> None:
7    if not validate_payload(payload):
8        return
9    print("event accepted", payload["event"])

Small functions plus early returns usually outperform complex nested branching in both readability and reliability.

Logging and Observability for Early Returns

Early exits are easy to read, but in production flows you still need visibility into why work was skipped. Add targeted logs for business-critical guards, and keep messages concise so signal remains high. If a guard can fail frequently, include counters in metrics dashboards to detect unusual changes.

python
1def process_payment(data: dict) -> None:
2    if not data.get("account_id"):
3        print("skip: missing account_id")
4        return
5
6    amount = data.get("amount")
7    if amount is None or amount <= 0:
8        print("skip: invalid amount")
9        return
10
11    print("payment accepted", data["account_id"], amount)
12
13process_payment({"amount": 10})
14process_payment({"account_id": "A-10", "amount": -1})
15process_payment({"account_id": "A-10", "amount": 15})

This keeps early-return code maintainable while still supporting audits and operational debugging.

A useful convention is to order guards by cost and likelihood. Put cheap and frequent checks first, then expensive validations later. This keeps runtime overhead low and makes hot paths easier to read.

Common Pitfalls

  • Using sys.exit inside library code instead of returning control to the caller.
  • Nesting conditionals deeply when a guard clause would flatten logic.
  • Returning mixed sentinel types that make caller behavior inconsistent.
  • Swallowing exceptional errors with return when exceptions are more appropriate.
  • Skipping logging for early exits in critical flows, which hides useful diagnostics.

Summary

  • return is the correct way to exit early in Python void-style functions.
  • Guard clauses improve clarity by handling invalid states upfront.
  • Type hints with -> None and plain return work well together.
  • Reserve exceptions for truly exceptional conditions.
  • Refactor long functions into smaller validators and executors for maintainability.

Course illustration
Course illustration

All Rights Reserved.