How to Implement Graceful Degradation with Feature Disabling in Python

A pattern that disables enhanced features and falls back to basic functionality when a dependency fails, with mock-based testing.

Medium Python 3.9+ Aug 9, 2026 Reliability & rate limiting 12 views 0 copies

Python code

50 lines
Python 3.9+
import random
from unittest.mock import patch


class EnhancedFeature:
    """A feature that can gracefully degrade when a dependency is unavailable."""

    def __init__(self):
        self.feature_enabled = True

    def get_enhanced_data(self):
        """Simulate an enhanced feature that depends on external data."""
        try:
            # Simulate potentially failing external API call
            mock_response = random.choice([True, False])
            if not mock_response:
                raise ConnectionError("External service unavailable")
            return {"level": "enhanced", "value": 42}
        except ConnectionError:
            self.feature_enabled = False
            return self.get_basic_data()

    def get_basic_data(self):
        """Fallback with reduced functionality."""
        return {"level": "basic", "value": 10}

    def is_capable(self):
        """Check if advanced features are available."""
        return self.feature_enabled


def demonstrate_degradation():
    feature = EnhancedFeature()

    # Simulate failure by patching random to always return False
    with patch("__main__.random.choice", return_value=False):
        result = feature.get_enhanced_data()
        print(f"With failure: {result}, capable={feature.is_capable()}")

    # Reset for normal operation
    feature.feature_enabled = True

    # Normal working case
    with patch("__main__.random.choice", return_value=True):
        result = feature.get_enhanced_data()
        print(f"Without failure: {result}, capable={feature.is_capable()}")


if __name__ == "__main__":
    demonstrate_degradation()

Output

stdout
With failure: {'level': 'basic', 'value': 10}, capable=False
Without failure: {'level': 'enhanced', 'value': 42}, capable=True

How it works

The EnhancedFeature class wraps a potentially failing external call in a try/except. When ConnectionError is raised, it sets feature_enabled to False and returns a degraded response from get_basic_data(). The is_capable() method exposes the degradation state for other parts of the system. Patches with unittest.mock simulate both failure and success paths deterministically, verifying that the feature degrades gracefully rather than crashing. This pattern is essential for building resilient systems that continue serving users even when supporting services go down.

Common mistakes

  • Letting exceptions propagate instead of catching them and falling back
  • Forgetting to reset the degraded state when the dependency recovers
  • Making the fallback logic as complex as the primary path, defeating the purpose
  • Not testing both the success and failure paths with mocks

Variations

  1. Use a circuit breaker pattern that opens after N consecutive failures and closes after a cooldown
  2. Implement a feature flag service to toggle enhanced capabilities remotely without redeploying

Real-world use cases

  • An e-commerce app that falls back to plain listings when personalized recommendations fail.
  • A dashboard that degrades from live WebSocket updates to periodic polling on connection loss.
  • A payment gateway that disables premium checkout options when the fraud-detection service times out.

Sponsored

Run this sample

Open the browser IDE to tweak the example and see results without installing anything.

Open editor

More from Reliability & rate limiting

Related tutorials and quizzes for this topic.