# Fail Fast

## Problem

Systems that silently accept invalid data or degrade gracefully on every error can propagate corruption deep into the stack. By the time the error surfaces, the root cause is far away and hard to diagnose. Data may be silently corrupted, logs are misleading, and debugging takes hours instead of minutes.

## Solution

**Fail fast**: detect errors at the earliest possible boundary and abort immediately with a clear error message. Don't try to "fix" bad input or proceed with partial data. Validate at system boundaries (API endpoints, message consumers, configuration loading) and reject invalid state before it propagates.

Key principles:
- Validate at the **boundary** (entry point), not deep inside.
- **Abort** or return an error immediately — don't accumulate partial results.
- Provide a **clear error message** with context about what was wrong and where.

## When to Use

- Configuration loading — fail at startup, not at first request 10 minutes later.
- API request validation — reject malformed requests at the handler, not in the database layer.
- Data pipeline ingestion — reject bad records at the source, not after transformations.
- Constructor / factory methods — don't create objects in an invalid state.

## When to Avoid

- Batch processing where you want to collect all errors (use "fail fast per item" but continue the batch).
- User-facing forms where you want to show all validation errors at once.
- Resilient systems that must degrade gracefully (circuit breakers, retry logic).

## Pseudocode

```
function load_config(path):
    if not file_exists(path):
        ABORT("Config file not found: " + path)

    config = parse(read_file(path))

    if config.database_url is empty:
        ABORT("Missing required config: database_url")
    if config.port < 1 or config.port > 65535:
        ABORT("Invalid port: " + config.port)

    return config   // Only reached if everything is valid

// App starts with validated config — no partial state
config = load_config("app.yaml")
start_server(config)
```

## Python

```python
"""Fail Fast pattern in Python."""
from dataclasses import dataclass


# ── Configuration object ──────────────────────────────────────

@dataclass(frozen=True)
class AppConfig:
    database_url: str
    port: int
    log_level: str


def load_config(raw: dict) -> AppConfig:
    """Validate and load config — fail fast on any issue."""

    # Fail fast: required field
    if "database_url" not in raw or not raw["database_url"]:
        raise SystemExit("FATAL: Missing required config: database_url")

    # Fail fast: port range
    port = raw.get("port", 8080)
    if not isinstance(port, int) or port < 1 or port > 65535:
        raise SystemExit(f"FATAL: Invalid port: {port}")

    # Fail fast: allowed log levels
    log_level = raw.get("log_level", "INFO")
    allowed = {"DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"}
    if log_level not in allowed:
        raise SystemExit(f"FATAL: Invalid log_level '{log_level}'. Allowed: {allowed}")

    return AppConfig(
        database_url=raw["database_url"],
        port=port,
        log_level=log_level,
    )


# ── API request validation ────────────────────────────────────

def process_order(order: dict) -> dict:
    """Fail fast on invalid order data."""

    if "customer_id" not in order:
        raise ValueError("Missing customer_id")

    if "items" not in order or not isinstance(order["items"], list):
        raise ValueError("items must be a non-empty list")

    if len(order["items"]) == 0:
        raise ValueError("items must be a non-empty list")

    for i, item in enumerate(order["items"]):
        if "sku" not in item:
            raise ValueError(f"Item {i}: missing sku")
        if "quantity" not in item or item["quantity"] < 1:
            raise ValueError(f"Item {i}: quantity must be >= 1")

    # All validation passed — safe to process
    total = sum(item.get("price", 0) * item["quantity"] for item in order["items"])
    return {"status": "accepted", "customer_id": order["customer_id"], "total": total}


# ── Tests ─────────────────────────────────────────────────────

def test_valid_config():
    cfg = load_config({"database_url": "postgres://localhost/db", "port": 5432})
    assert cfg.port == 5432
    print(f"PASS: valid config loaded: port={cfg.port}, db={cfg.database_url}")


def test_missing_database_url_fails():
    try:
        load_config({"port": 5432})
        assert False
    except SystemExit as e:
        assert "database_url" in str(e)
        print(f"PASS: missing db_url -> {e}")


def test_invalid_port_fails():
    try:
        load_config({"database_url": "postgres://x", "port": 99999})
        assert False
    except SystemExit as e:
        assert "Invalid port" in str(e)
        print(f"PASS: bad port -> {e}")


def test_invalid_log_level_fails():
    try:
        load_config({"database_url": "postgres://x", "log_level": "VERBOSE"})
        assert False
    except SystemExit as e:
        assert "Invalid log_level" in str(e)
        print(f"PASS: bad log_level -> {e}")


def test_valid_order():
    result = process_order({
        "customer_id": "C001",
        "items": [{"sku": "W1", "quantity": 2, "price": 9.99}],
    })
    assert result["status"] == "accepted"
    print(f"PASS: valid order -> {result}")


def test_order_missing_customer():
    try:
        process_order({"items": [{"sku": "W1", "quantity": 1}]})
        assert False
    except ValueError as e:
        print(f"PASS: missing customer -> {e}")


def test_order_bad_quantity():
    try:
        process_order({"customer_id": "C1", "items": [{"sku": "W1", "quantity": 0}]})
        assert False
    except ValueError as e:
        print(f"PASS: bad quantity -> {e}")


if __name__ == "__main__":
    test_valid_config()
    test_missing_database_url_fails()
    test_invalid_port_fails()
    test_invalid_log_level_fails()
    test_valid_order()
    test_order_missing_customer()
    test_order_bad_quantity()
    print("\nAll Fail Fast tests passed.")
```

## Go

```go
// fail_fast.go
package main

import (
	"fmt"
	"strings"
)

// ── Config ───────────────────────────────────────────────────

type AppConfig struct {
	DatabaseURL string
	Port        int
	LogLevel    string
}

func LoadConfig(raw map[string]interface{}) (*AppConfig, error) {
	dbURL, ok := raw["database_url"].(string)
	if !ok || dbURL == "" {
		return nil, fmt.Errorf("FATAL: Missing required config: database_url")
	}

	port := 8080
	if p, ok := raw["port"].(int); ok {
		port = p
	}
	if port < 1 || port > 65535 {
		return nil, fmt.Errorf("FATAL: Invalid port: %d", port)
	}

	logLevel := "INFO"
	if ll, ok := raw["log_level"].(string); ok {
		logLevel = ll
	}
	allowed := map[string]bool{"DEBUG": true, "INFO": true, "WARNING": true, "ERROR": true, "CRITICAL": true}
	if !allowed[logLevel] {
		return nil, fmt.Errorf("FATAL: Invalid log_level '%s'", logLevel)
	}

	return &AppConfig{DatabaseURL: dbURL, Port: port, LogLevel: logLevel}, nil
}

// ── Order processing ─────────────────────────────────────────

type OrderItem struct {
	SKU      string
	Quantity int
	Price    float64
}

type Order struct {
	CustomerID string
	Items      []OrderItem
}

func ProcessOrder(o Order) (string, error) {
	if o.CustomerID == "" {
		return "", fmt.Errorf("missing customer_id")
	}
	if len(o.Items) == 0 {
		return "", fmt.Errorf("items must be a non-empty list")
	}
	for i, item := range o.Items {
		if item.SKU == "" {
			return "", fmt.Errorf("item %d: missing sku", i)
		}
		if item.Quantity < 1 {
			return "", fmt.Errorf("item %d: quantity must be >= 1", i)
		}
	}

	total := 0.0
	for _, item := range o.Items {
		total += item.Price * float64(item.Quantity)
	}
	return fmt.Sprintf("accepted (total=%.2f)", total), nil
}

// ── Tests ────────────────────────────────────────────────────

func testValidConfig() {
	cfg, err := LoadConfig(map[string]interface{}{"database_url": "postgres://localhost/db", "port": 5432})
	if err != nil {
		panic(err)
	}
	fmt.Printf("PASS: valid config: port=%d, db=%s\n", cfg.Port, cfg.DatabaseURL)
}

func testMissingDBURL() {
	_, err := LoadConfig(map[string]interface{}{"port": 5432})
	if err == nil || !strings.Contains(err.Error(), "database_url") {
		panic("expected database_url error")
	}
	fmt.Printf("PASS: missing db_url -> %v\n", err)
}

func testInvalidPort() {
	_, err := LoadConfig(map[string]interface{}{"database_url": "pg://x", "port": 99999})
	if err == nil {
		panic("expected error")
	}
	fmt.Printf("PASS: bad port -> %v\n", err)
}

func testValidOrder() {
	result, err := ProcessOrder(Order{
		CustomerID: "C001",
		Items:      []OrderItem{{SKU: "W1", Quantity: 2, Price: 9.99}},
	})
	if err != nil {
		panic(err)
	}
	fmt.Printf("PASS: valid order -> %s\n", result)
}

func testOrderMissingCustomer() {
	_, err := ProcessOrder(Order{Items: []OrderItem{{SKU: "W1", Quantity: 1}}})
	if err == nil {
		panic("expected error")
	}
	fmt.Printf("PASS: missing customer -> %v\n", err)
}

func main() {
	testValidConfig()
	testMissingDBURL()
	testInvalidPort()
	testValidOrder()
	testOrderMissingCustomer()
	fmt.Println("\nAll Fail Fast tests passed.")
}
```

## JavaScript

```javascript
// fail_fast.js

// ── Config ───────────────────────────────────────────────────

function loadConfig(raw) {
  if (!raw.database_url) {
    throw new Error("FATAL: Missing required config: database_url");
  }

  const port = raw.port ?? 8080;
  if (!Number.isInteger(port) || port < 1 || port > 65535) {
    throw new Error(`FATAL: Invalid port: ${port}`);
  }

  const logLevel = raw.log_level ?? "INFO";
  const allowed = new Set(["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]);
  if (!allowed.has(logLevel)) {
    throw new Error(`FATAL: Invalid log_level '${logLevel}'`);
  }

  return Object.freeze({
    databaseUrl: raw.database_url,
    port,
    logLevel,
  });
}

// ── Order processing ─────────────────────────────────────────

function processOrder(order) {
  if (!order.customerId) throw new Error("Missing customer_id");
  if (!Array.isArray(order.items) || order.items.length === 0) {
    throw new Error("items must be a non-empty list");
  }
  order.items.forEach((item, i) => {
    if (!item.sku) throw new Error(`Item ${i}: missing sku`);
    if (!item.quantity || item.quantity < 1)
      throw new Error(`Item ${i}: quantity must be >= 1`);
  });

  const total = order.items.reduce(
    (sum, it) => sum + (it.price || 0) * it.quantity,
    0
  );
  return { status: "accepted", customerId: order.customerId, total };
}

// ── Tests ────────────────────────────────────────────────────

function testValidConfig() {
  const cfg = loadConfig({ database_url: "postgres://localhost/db", port: 5432 });
  console.assert(cfg.port === 5432);
  console.log(`PASS: valid config: port=${cfg.port}, db=${cfg.databaseUrl}`);
}

function testMissingDBURL() {
  try {
    loadConfig({ port: 5432 });
    throw new Error("expected");
  } catch (e) {
    console.assert(e.message.includes("database_url"));
    console.log(`PASS: missing db_url -> ${e.message}`);
  }
}

function testInvalidPort() {
  try {
    loadConfig({ database_url: "pg://x", port: 99999 });
    throw new Error("expected");
  } catch (e) {
    console.log(`PASS: bad port -> ${e.message}`);
  }
}

function testValidOrder() {
  const result = processOrder({
    customerId: "C001",
    items: [{ sku: "W1", quantity: 2, price: 9.99 }],
  });
  console.assert(result.status === "accepted");
  console.log(`PASS: valid order -> ${JSON.stringify(result)}`);
}

function testOrderMissingCustomer() {
  try {
    processOrder({ items: [{ sku: "W1", quantity: 1 }] });
    throw new Error("expected");
  } catch (e) {
    console.log(`PASS: missing customer -> ${e.message}`);
  }
}

testValidConfig();
testMissingDBURL();
testInvalidPort();
testValidOrder();
testOrderMissingCustomer();
console.log("\nAll Fail Fast tests passed.");
```

## Related Patterns

- **Guard Clause** — guard clauses are the implementation pattern for fail-fast validation at function entry points.
- **Specification** — a specification encapsulates business rules; fail-fast validates against specifications at system boundaries.
- **Assertion** — assertions are the lowest-level fail-fast mechanism, enforcing invariants that should never be violated.
