Fun with Learning Technology
LearnCoursesQuestionsTracksToolsNewsExplorePractice
Fun with Learning Technology

A new problem, explained clearly, every day.

Subscribe
Learn
  • Lessons
  • Topics
  • News
  • Tools
  • Courses
  • Career tracks
  • Everything
Site
  • About
  • Contact
  • Support
  • Privacy
  • Terms
Get the daily one

One email per new problem. No spam.

Request a tutorial

Requests shape what gets made next.

© 2026 Fun with Learning TechnologyRSS
Home›Courses›System Design›Fault Tolerance and Graceful Degradation

Reliability

Fault Tolerance and Graceful Degradation

Fault tolerance is the capacity of a system to maintain operational integrity despite the failure of individual components. Graceful degradation allows a system to shed non-essential functionality rather than failing entirely, preserving the core user experience. Designers prioritize these concepts when high availability and robustness are critical requirements for service delivery.

The Fundamental Principle of Redundancy

Redundancy is the cornerstone of fault tolerance, predicated on the elimination of single points of failure. By maintaining multiple instances of a service, the system ensures that if one node crashes due to hardware errors or software defects, traffic can be rerouted without impacting the end user. The reasoning here is probabilistic: the likelihood of two independent components failing simultaneously is significantly lower than that of a single component failing. To implement this effectively, we must use load balancers that perform health checks to identify and remove unresponsive nodes from the active rotation. Without this mechanism, the system would continue attempting to route requests to dead instances, causing latency spikes and timeouts. Therefore, redundancy is not just about having more hardware; it is about creating an automated environment where the architecture actively heals itself by isolating failures from the remaining operational pool.

def get_healthy_node(nodes):
    # Filter nodes that report an 'UP' status
    # This prevents sending traffic to failed instances
    healthy = [n for n in nodes if n.is_alive()]
    return healthy[0] if healthy else None

# Simulated nodes: node_a is down
nodes = [{'id': 1, 'is_alive': lambda: True}, {'id': 2, 'is_alive': lambda: False}]
print(get_healthy_node(nodes))

Timeouts and Exponential Backoff

Network partitions and slow dependencies are common sources of cascading failures. If a service waits indefinitely for a response from a downstream component, it will eventually exhaust its own worker threads, leading to a total system freeze. Implementing strict timeouts is the first line of defense, forcing the calling service to release resources after a specified duration. However, retrying immediately after a timeout can overwhelm a struggling service, worsening the situation. This is why we implement exponential backoff with jitter. By increasing the wait interval between retries and adding a random variance (jitter), we prevent the 'thundering herd' problem where multiple clients hit a recovering server at exactly the same time. This strategy protects the downstream dependency during its recovery phase, allowing it to re-establish stability without being immediately bombarded by a backlog of queued requests.

import time
import random

def retry_with_backoff(operation, retries=3):
    for i in range(retries):
        try:
            return operation()
        except Exception:
            # Exponential backoff: 2, 4, 8 seconds + random jitter
            wait = (2 ** i) + random.uniform(0, 1)
            time.sleep(wait)
    raise Exception('Dependency unavailable after retries')

retry_with_backoff(lambda: print('Requesting...'))

Circuit Breakers for System Protection

A circuit breaker is a sophisticated state machine used to detect when a downstream service has entered a failed state. Instead of constantly hammering a broken endpoint, the breaker trips, and all subsequent calls are immediately rejected or routed to a fallback path. This provides the downstream service the breathing room it needs to recover, while also saving the upstream system from wasting resources on doomed requests. The logic transitions through three states: Closed (normal operation), Open (tripped, failures blocked), and Half-Open (testing if the service has recovered). By implementing this, we effectively decouple the availability of our system from the health of our dependencies. It transforms the system from a fragile chain of components into a resilient ecosystem where failures are contained within specific boundaries, ensuring that an issue in a non-critical microservice does not collapse the entire user-facing architecture.

class CircuitBreaker:
    def __init__(self): self.state = 'CLOSED'
    def call(self, func):
        if self.state == 'OPEN': return 'Fallback Response'
        try:
            return func()
        except: # If exception occurs, trip the circuit
            self.state = 'OPEN'
            return 'Fallback Response'

cb = CircuitBreaker()
print(cb.call(lambda: 1/0))

Load Shedding and Resource Throttling

When a system experiences a sudden surge in traffic, it may lack the raw compute capacity to process every request successfully. Load shedding is the deliberate rejection of lower-priority requests to protect the integrity of the core user experience. The reasoning is simple: it is better to serve 90% of requests successfully than to allow the entire system to crash under load, serving 0%. We classify traffic based on importance, such as login or data processing being 'critical' while analytics or reporting might be 'optional'. By monitoring system metrics like memory usage or CPU saturation, we can set thresholds that trigger the dropping of optional requests. This ensures that the system maintains a stable state for core operations even during unprecedented traffic spikes or resource exhaustion events. It acts as a safety valve, preserving the system's core value proposition while managing throughput intelligently under stress.

def process_request(priority, current_load):
    # Shed low priority traffic if system is at 90% capacity
    if current_load > 0.9 and priority == 'LOW':
        return '503 Service Unavailable'
    return '200 OK'

print(process_request('LOW', 0.95))

Graceful Degradation via Fallbacks

Graceful degradation is the practice of designing components so that they remain functional even when certain non-critical features are unavailable. If a primary service (like a personalized recommendation engine) fails, the system should not return an error page; instead, it should fallback to a static, cached response (like a list of top-selling items). This preserves the user's workflow, allowing them to continue their tasks while the engineers fix the underlying issue. The reasoning here relies on the separation of 'critical path' and 'enhancement' features. We define the minimum viable response for every request. By preparing these fallback paths in advance, we decouple user experience from backend health. This proactive design philosophy ensures that the system provides value under all conditions, transforming failure from a catastrophic event into a minor, transparent shift in the richness of the provided experience.

def get_recommendations(user_id):
    try:
        # Attempt primary service
        return call_ml_engine(user_id)
    except:
        # Fallback to static, pre-computed list if service fails
        return ['Global Top 10', 'Best Sellers']

def call_ml_engine(user_id): raise Exception('Service down')
print(get_recommendations(123))

Key points

  • Redundancy eliminates single points of failure by maintaining multiple operational nodes.
  • Timeouts prevent resource exhaustion by capping the time a service waits for dependencies.
  • Exponential backoff with jitter reduces pressure on services recovering from failures.
  • Circuit breakers stop the propagation of errors by isolating failing components from the system.
  • Load shedding ensures system stability by dropping non-essential tasks during high traffic.
  • Graceful degradation maintains a baseline user experience during partial system failures.
  • The core logic of fault tolerance is to manage failure states rather than trying to prevent them entirely.
  • Priority-based routing is essential for effective resource management during times of extreme stress.

Common mistakes

  • Mistake: Equating fault tolerance with high availability. Why it's wrong: Fault tolerance refers to a system's ability to operate after a component fails, whereas high availability describes the duration of system uptime. Fix: Focus on error recovery mechanisms for fault tolerance and redundancy for availability.
  • Mistake: Assuming a hard reset is an acceptable form of graceful degradation. Why it's wrong: Graceful degradation should maintain core functionality rather than forcing a total restart. Fix: Implement circuit breakers or feature flags to disable non-essential services.
  • Mistake: Over-relying on automated retries without backoff strategies. Why it's wrong: Blind retries during a partial outage cause cascading failures by overwhelming the downstream service. Fix: Use exponential backoff and jitter to spread the load.
  • Mistake: Designing for failure in the primary path but ignoring the recovery process. Why it's wrong: A system that fails over but cannot revert or sync state properly will experience inconsistent data. Fix: Test automated failback and state reconciliation protocols.
  • Mistake: Ignoring human-in-the-loop or manual overrides. Why it's wrong: Automated graceful degradation can misinterpret traffic spikes as system failure. Fix: Provide manual circuit breaker controls to allow human intervention during unexpected behavior.

Interview questions

What is the fundamental difference between fault tolerance and graceful degradation?

Fault tolerance is the system's ability to continue operating properly in the event of the failure of one or more of its components, often through redundancy. It aims to prevent downtime entirely by having backups ready to take over. Graceful degradation, conversely, is the design philosophy where a system maintains limited functionality even when a significant portion of the system has failed, sacrificing non-essential features to keep core services alive. While fault tolerance attempts to hide the failure from the user, graceful degradation acknowledges the failure and ensures the user still has access to the most critical system capabilities.

How can a circuit breaker pattern be used to improve system resilience?

The circuit breaker pattern prevents a system from repeatedly trying to execute an operation that is likely to fail. It sits between a client and a service, monitoring for errors. When failures cross a threshold, the breaker 'trips,' and subsequent calls immediately return an error or fallback response without hitting the failing service. This prevents cascading failures and gives the struggling downstream system time to recover. Once a timeout occurs, the breaker enters a half-open state to test if the service is operational again, automatically restoring the connection if the service succeeds.

Compare Retries with Exponential Backoff versus Dead Letter Queues for handling transient failures.

Retries with exponential backoff are ideal for transient, short-lived issues like network blips. By increasing the wait time between attempts, you prevent overwhelming a service that might be struggling, effectively 'throttling' the retry load. Dead Letter Queues (DLQs) are for non-transient or persistent failures where retrying immediately will never result in success. Instead of blocking the processing pipeline, you move the failing message to a separate queue for offline debugging. Retries handle temporary fluctuations, while DLQs preserve data integrity for messages that require manual intervention or secondary processing logic.

What is the role of load balancers in maintaining system availability and fault tolerance?

Load balancers serve as a critical gateway, distributing incoming traffic across multiple healthy server nodes. From a fault tolerance perspective, they perform active health checks on backend services; if a node stops responding to heartbeat signals, the load balancer stops sending it traffic. This ensures that users are never routed to a 'dead' instance. Furthermore, load balancers enable seamless deployments, as you can drain traffic from nodes without impacting the availability of the application, effectively acting as the first line of defense against service-level failures.

How do you design a system to handle partial failures when communicating with external dependencies?

To handle partial failures, you must embrace asynchronous communication and robust timeout strategies. You should never allow an external dependency to block your main execution thread indefinitely. By implementing strict timeouts, you ensure the caller eventually regains control. Additionally, you should employ the 'bulkhead' pattern, where you isolate resources for different services so that a failure in one, like an external payment API, cannot consume the thread pool needed for other operations. Using queues allows you to decouple services, ensuring that even if a dependency is temporarily unavailable, the system continues to accept requests safely.

Explain the concept of 'Static Stability' in the context of large-scale distributed systems.

Static stability is the principle that a system should be able to continue functioning even if the control plane or management services are unavailable. In a dynamic system, many components rely on a central service, like a configuration server, to operate. If that service fails, the entire system can crumble. A statically stable system caches configuration locally or operates with default 'last-known-good' values. For example, if your service discovery layer fails, your nodes should continue routing to the IP addresses they were using before the failure occurred rather than shutting down. This reduces the blast radius of control plane outages significantly.

All System Design interview questions →

Check yourself

1. A microservice cluster experiences latency spikes in a downstream dependency. Which strategy best exemplifies graceful degradation?

  • A.Returning a cached stale response or a default value instead of failing the request.
  • B.Increasing the timeout threshold to ensure the request eventually completes.
  • C.Terminating all active connections to the downstream service to clear the buffer.
  • D.Performing a rolling restart of all nodes to reset the network stack.
Show answer

A. Returning a cached stale response or a default value instead of failing the request.
Returning cached data maintains core system utility. Increasing timeouts risks request pile-up (wrong), terminating connections is aggressive and potentially destructive (wrong), and a restart causes unnecessary downtime (wrong).

2. In a distributed system, what is the primary role of the Circuit Breaker pattern?

  • A.To encrypt all traffic between services to ensure fault-tolerant communication.
  • B.To prevent a request from hitting a failing service repeatedly, allowing it time to recover.
  • C.To balance the load equally across all nodes to ensure no single node hits a fault condition.
  • D.To automatically trigger a failover to a disaster recovery region if latency increases by 5ms.
Show answer

B. To prevent a request from hitting a failing service repeatedly, allowing it time to recover.
The circuit breaker halts calls to failing services to prevent cascading failures. Encryption is security, not fault tolerance (wrong). Load balancing doesn't handle component failure (wrong). A 5ms increase is too trivial to trigger a disaster recovery failover (wrong).

3. Why is 'jitter' combined with 'exponential backoff' during retries?

  • A.To ensure the system logs the failure timestamp with high precision.
  • B.To prevent a 'thundering herd' problem where many clients retry simultaneously.
  • C.To reduce the cryptographic overhead required for secure retry communication.
  • D.To prioritize traffic from high-paying users during a system degradation event.
Show answer

B. To prevent a 'thundering herd' problem where many clients retry simultaneously.
Jitter adds randomness to retry intervals, preventing synchronized spikes of traffic. Precision logging is not the goal of backoff (wrong). Cryptographic overhead is unrelated (wrong). Prioritization is a separate mechanism from retry strategy (wrong).

4. Which scenario indicates a system is failing to degrade gracefully?

  • A.The system disables profile picture rendering when the image service is slow.
  • B.The entire website returns a 500 internal server error because the recommendation engine is down.
  • C.The system switches from a primary database to a read-only secondary node.
  • D.The system throttles API requests from non-authenticated users during a traffic surge.
Show answer

B. The entire website returns a 500 internal server error because the recommendation engine is down.
A full 500 error signifies a total collapse of functionality rather than partial degradation. Disabling one feature is graceful (wrong). Using a secondary node is a standard redundancy pattern (wrong). Throttling is a controlled degradation technique (wrong).

5. Which design choice best supports fault tolerance in an asynchronous task processing system?

  • A.Storing tasks in a volatile memory queue to ensure low-latency processing.
  • B.Implementing idempotent consumers so tasks can be safely retried without side effects.
  • C.Using a single master node to coordinate all task distribution to avoid consistency issues.
  • D.Eliminating all logging to maximize the CPU available for task execution.
Show answer

B. Implementing idempotent consumers so tasks can be safely retried without side effects.
Idempotency is crucial for fault tolerance because it allows retrying failures safely. Volatile memory loses state on crash (wrong). A single master is a single point of failure (wrong). Removing logs makes debugging failures impossible (wrong).

Take the full System Design quiz →

← PreviousHigh Availability — 99.9% vs 99.99%Next →Monitoring, Alerting, and Observability

System Design

31 lessons, free to read.

All lessons →

Track your progress

Sign in to mark lessons done, score quizzes and keep notes.

Open in the app