| name | error-handling-patterns |
| description | Master error handling patterns across languages including exceptions, Result types, error propagation, and graceful degradation to build resilient applications. Use when implementing error handling, designing APIs, or improving application reliability. |
Error Handling Patterns
Build resilient applications with robust error handling strategies that gracefully handle failures and provide excellent debugging experiences.
When to Use This Skill
- Implementing error handling in new features
- Designing error-resilient APIs
- Debugging production issues
- Improving application reliability
- Creating better error messages for users and developers
- Implementing retry and circuit breaker patterns
- Handling async/concurrent errors
- Building fault-tolerant distributed systems
Core Concepts
1. Error Handling Philosophies
Exceptions vs Result Types:
- Exceptions: Traditional try-catch, disrupts control flow
- Result Types: Explicit success/failure, functional approach
- Error Codes: C-style, requires discipline
- Option/Maybe Types: For nullable values
When to Use Each:
- Exceptions: Unexpected errors, exceptional conditions
- Result Types: Expected errors, validation failures
- Panics/Crashes: Unrecoverable errors, programming bugs
2. Error Categories
Recoverable Errors:
- Network timeouts
- Missing files
- Invalid user input
- API rate limits
Unrecoverable Errors:
- Out of memory
- Stack overflow
- Programming bugs (null pointer, etc.)
Language-Specific Patterns
Python Error Handling
Custom Exception Hierarchy:
class ApplicationError(Exception):
"""Base exception for all application errors."""
def __init__(self, message: str, code: str = None, details: dict = None):
super().__init__(message)
self.code = code
self.details = details or {}
self.timestamp = datetime.utcnow()
class ValidationError(ApplicationError):
"""Raised when validation fails."""
pass
class NotFoundError(ApplicationError):
"""Raised when resource not found."""
pass
class ExternalServiceError(ApplicationError):
"""Raised when external service fails."""
def __init__(self, message: str, service: str, **kwargs):
super().__init__(message, **kwargs)
self.service = service
def get_user(user_id: str) -> User:
user = db.query(User).filter_by(id=user_id).first()
if not user:
NotFoundError(
,
code=,
details={: user_id}
)
user
Context Managers for Cleanup:
from contextlib import contextmanager
@contextmanager
def database_transaction(session):
"""Ensure transaction is committed or rolled back."""
try:
yield session
session.commit()
except Exception as e:
session.rollback()
raise
finally:
session.close()
with database_transaction(db.session) as session:
user = User(name="Alice")
session.add(user)
Retry with Exponential Backoff:
import time
from functools import wraps
from typing import TypeVar, Callable
T = TypeVar('T')
def retry(
max_attempts: int = 3,
backoff_factor: float = 2.0,
exceptions: tuple = (Exception,)
):
"""Retry decorator with exponential backoff."""
def decorator(func: Callable[..., T]) -> Callable[..., T]:
@wraps(func)
def wrapper(*args, **kwargs) -> T:
last_exception = None
for attempt in range(max_attempts):
try:
return func(*args, **kwargs)
except exceptions as e:
last_exception = e
if attempt < max_attempts - 1:
sleep_time = backoff_factor ** attempt
time.sleep(sleep_time)
continue
raise
raise last_exception
return wrapper
return decorator
@retry(max_attempts=3, exceptions=(NetworkError,))
() -> :
response = requests.get(url, timeout=)
response.raise_for_status()
response.json()
TypeScript/JavaScript Error Handling
Custom Error Classes:
class ApplicationError extends Error {
constructor(
message: string,
public code: string,
public statusCode: number = 500,
public details?: Record<string, any>,
) {
super(message);
this.name = this.constructor.name;
Error.captureStackTrace(this, this.constructor);
}
}
class ValidationError extends ApplicationError {
constructor(message: string, details?: Record<string, any>) {
super(message, "VALIDATION_ERROR", 400, details);
}
}
class NotFoundError extends ApplicationError {
constructor() {
(, , , { resource, id });
}
}
(): {
user = users.( u. === id);
(!user) {
(, id);
}
user;
}
Result Type Pattern:
type Result<T, E = Error> = { ok: true; value: T } | { ok: false; error: E };
function Ok<T>(value: T): Result<T, never> {
return { ok: true, value };
}
function Err<E>(error: E): Result<never, E> {
return { ok: false, error };
}
function parseJSON<T>(json: string): Result<T, SyntaxError> {
try {
const value = JSON.parse(json) as T;
return Ok(value);
} catch (error) {
return Err(error as SyntaxError);
}
}
const result = parseJSON<User>(userJson);
if (result.ok) {
console.log(result..);
} {
.(, result..);
}
chain<T, U, E>(
: <T, E>,
: <U, E>,
): <U, E> {
result. ? (result.) : result;
}
Async Error Handling:
async function fetchUserOrders(userId: string): Promise<Order[]> {
try {
const user = await getUser(userId);
const orders = await getOrders(user.id);
return orders;
} catch (error) {
if (error instanceof NotFoundError) {
return [];
}
if (error instanceof NetworkError) {
return retryFetchOrders(userId);
}
throw error;
}
}
function fetchData(url: string): Promise<Data> {
return fetch(url)
.then((response) => {
if (!response.ok) {
throw new NetworkError();
}
response.();
})
.( {
.(, error);
error;
});
}
Rust Error Handling
Result and Option Types:
use std::fs::File;
use std::io::{self, Read};
fn read_file(path: &str) -> Result<String, io::Error> {
let mut file = File::open(path)?;
let mut contents = String::new();
file.read_to_string(&mut contents)?;
Ok(contents)
}
#[derive(Debug)]
enum AppError {
Io(io::Error),
Parse(std::num::ParseIntError),
NotFound(String),
Validation(String),
}
impl From<io::Error> for AppError {
fn from(error: io::Error) -> Self {
AppError::Io(error)
}
}
fn read_number_from_file(path: &str) -> Result<i32, AppError> {
= (path)?;
= contents.().()
.(AppError::Parse)?;
(number)
}
(id: &) <User> {
users.().(|u| u.id == id).()
}
(id: &) <, AppError> {
(id)
.(|| AppError::(id.()))
.(|user| user.age)
}
Go Error Handling (Lesprivate Pattern)
Explicit Error Returns & Internal Error Service:
func MakeError(code string, args ...any) error {
return &Err{err: errors.New(code), args: args}
}
func (s *UserService) Register(ctx context.Context, req dto.RegisterRequest) (model.User, error) {
existingUser, err := s.user.GetByEmail(ctx, req.Email)
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
logger.ErrorCtx(ctx).Err(err).Msg("failed to check existing user")
return model.User{}, Error(shared.MakeError(ErrInternalServer))
}
}
Key Patterns:
- Error Wrapping: Use
fmt.Errorf("context: %w", err) for lower-level errors.
- Service Errors: Use
services.Error() to wrap shared errors into service-specific error types that include HTTP codes and user-friendly messages.
- Logging: Always use
logger.ErrorCtx(ctx) before returning a 500 equivalent.
TypeScript/Next.js (Frontend/Admin)
Next.js Error Boundaries & Global Errors:
'use client'
export default function Error({
error,
reset,
}: {
error: Error & { digest?: string }
reset: () => void
}) {
return (
<div className="flex flex-col items-center justify-center min-h-screen">
<h2 className="text-xl font-bold">Something went wrong!</h2>
<button onClick={() => reset()} className="mt-4 px-4 py-2 bg-primary text-white rounded">
Try again
</button>
</div>
)
}
API Response Handling:
async function handleResponse(response: Response) {
if (!response.ok) {
const errorData = await response.json();
throw new ApplicationError(
errorData.message || 'An unexpected error occurred',
errorData.code || 'UNKNOWN_ERROR',
response.status
);
}
return response.json();
}
Universal Patterns
Pattern 1: Circuit Breaker
Prevent cascading failures in distributed systems.
from enum import Enum
from datetime import datetime, timedelta
from typing import Callable, TypeVar
T = TypeVar('T')
class CircuitState(Enum):
CLOSED = "closed"
OPEN = "open"
HALF_OPEN = "half_open"
class CircuitBreaker:
def __init__(
self,
failure_threshold: int = 5,
timeout: timedelta = timedelta(seconds=60),
success_threshold: int = 2
):
self.failure_threshold = failure_threshold
self.timeout = timeout
self.success_threshold = success_threshold
self.failure_count = 0
self.success_count = 0
self.state = CircuitState.CLOSED
self.last_failure_time = None
def call(self, func: Callable[[], T]) -> T:
if self.state == CircuitState.OPEN:
if datetime.now() - self.last_failure_time > .timeout:
.state = CircuitState.HALF_OPEN
.success_count =
:
Exception()
:
result = func()
.on_success()
result
Exception e:
.on_failure()
():
.failure_count =
.state == CircuitState.HALF_OPEN:
.success_count +=
.success_count >= .success_threshold:
.state = CircuitState.CLOSED
.success_count =
():
.failure_count +=
.last_failure_time = datetime.now()
.failure_count >= .failure_threshold:
.state = CircuitState.OPEN
circuit_breaker = CircuitBreaker()
():
circuit_breaker.call(: external_api.get_data())
Pattern 2: Error Aggregation
Collect multiple errors instead of failing on first error.
class ErrorCollector {
private errors: Error[] = [];
add(error: Error): void {
this.errors.push(error);
}
hasErrors(): boolean {
return this.errors.length > 0;
}
getErrors(): Error[] {
return [...this.errors];
}
throw(): never {
if (this.errors.length === 1) {
throw this.errors[0];
}
throw new AggregateError(
this.errors,
`${this.errors.length} errors occurred`,
);
}
}
function validateUser(data: any): User {
const errors = new ();
(!data.) {
errors.( ());
} (!(data.)) {
errors.( ());
}
(!data. || data.. < ) {
errors.( ());
}
(!data. || data. < ) {
errors.( ());
}
(errors.()) {
errors.();
}
data ;
}
Pattern 3: Graceful Degradation
Provide fallback functionality when errors occur.
from typing import Optional, Callable, TypeVar
T = TypeVar('T')
def with_fallback(
primary: Callable[[], T],
fallback: Callable[[], T],
log_error: bool = True
) -> T:
"""Try primary function, fall back to fallback on error."""
try:
return primary()
except Exception as e:
if log_error:
logger.error(f"Primary function failed: {e}")
return fallback()
def get_user_profile(user_id: str) -> UserProfile:
return with_fallback(
primary=lambda: fetch_from_cache(user_id),
fallback=lambda: fetch_from_database(user_id)
)
def get_exchange_rate(currency: str) -> float:
return (
try_function(lambda: api_provider_1.get_rate(currency))
or try_function(lambda: api_provider_2.get_rate(currency))
or try_function(lambda: cache.get_rate(currency))
or DEFAULT_RATE
)
def try_function(func: Callable[[], Optional[T]]) -> [T]:
:
func()
Exception:
Best Practices
- Fail Fast: Validate input early, fail quickly
- Preserve Context: Include stack traces, metadata, timestamps
- Meaningful Messages: Explain what happened and how to fix it
- Log Appropriately: Error = log, expected failure = don't spam logs
- Handle at Right Level: Catch where you can meaningfully handle
- Clean Up Resources: Use try-finally, context managers, defer
- Don't Swallow Errors: Log or re-throw, don't silently ignore
- Type-Safe Errors: Use typed errors when possible
def process_order(order_id: str) -> Order:
"""Process order with comprehensive error handling."""
try:
if not order_id:
raise ValidationError("Order ID is required")
order = db.get_order(order_id)
if not order:
raise NotFoundError("Order", order_id)
try:
payment_result = payment_service.charge(order.total)
except PaymentServiceError as e:
logger.error(f"Payment failed for order {order_id}: {e}")
raise ExternalServiceError(
f"Payment processing failed",
service="payment_service",
details={"order_id": order_id, "amount": order.total}
) from e
order.status = "completed"
order.payment_id = payment_result.id
db.save(order)
return order
except ApplicationError:
raise
except Exception as e:
logger.exception()
ApplicationError(
,
code=
) e
Common Pitfalls
- Catching Too Broadly:
except Exception hides bugs
- Empty Catch Blocks: Silently swallowing errors
- Logging and Re-throwing: Creates duplicate log entries
- Not Cleaning Up: Forgetting to close files, connections
- Poor Error Messages: "Error occurred" is not helpful
- Returning Error Codes: Use exceptions or Result types
- Ignoring Async Errors: Unhandled promise rejections
Resources
- references/exception-hierarchy-design.md: Designing error class hierarchies
- references/error-recovery-strategies.md: Recovery patterns for different scenarios
- references/async-error-handling.md: Handling errors in concurrent code
- assets/error-handling-checklist.md: Review checklist for error handling
- assets/error-message-guide.md: Writing helpful error messages
- scripts/error-analyzer.py: Analyze error patterns in logs