DEV Community

Sebastien Lato
Sebastien Lato

Posted on

SwiftUI Circuit Breakers & Network Resilience Patterns (Prevent Cascading Failures)

Most apps call APIs like this:

try await api.fetchUser()
Enter fullscreen mode Exit fullscreen mode

That works…

until the server starts failing.

Then your app does this:

  • retries immediately
  • sends hundreds of failing requests
  • drains battery
  • overloads the backend
  • slows down the UI
  • worsens the outage

This is called a cascading failure.

A production app needs protection mechanisms.

This post shows how to implement circuit breakers and network resilience patterns in SwiftUI that are:

  • failure-aware
  • backend-friendly
  • battery-conscious
  • retry-safe
  • production-grade

🧠 The Core Principle

When a system is failing, stop making it worse.

Instead of retrying endlessly, the system must detect failures and pause requests.


🧱 1. What Is a Circuit Breaker?

A circuit breaker prevents repeated calls to a failing service.

It has three states:

Closed β†’ Normal operation
Open β†’ Requests blocked
Half-Open β†’ Testing recovery
Enter fullscreen mode Exit fullscreen mode

Flow:

Requests Fail
β†’ Circuit Opens
β†’ Requests Blocked
β†’ Recovery Test
β†’ Circuit Closes
Enter fullscreen mode Exit fullscreen mode

πŸ”Œ 2. Define Circuit States

enum CircuitState {
    case closed
    case open(until: Date)
    case halfOpen
}
Enter fullscreen mode Exit fullscreen mode

Meaning:

  • Closed β†’ requests allowed
  • Open β†’ requests blocked temporarily
  • Half-open β†’ allow limited requests to test recovery

🧬 3. Basic Circuit Breaker Implementation

final class CircuitBreaker {

    private(set) var state: CircuitState = .closed
    private var failureCount = 0
    private let failureThreshold = 5
    private let timeout: TimeInterval = 30

    func recordFailure() {
        failureCount += 1

        if failureCount >= failureThreshold {
            state = .open(until: Date().addingTimeInterval(timeout))
        }
    }

    func recordSuccess() {
        failureCount = 0
        state = .closed
    }
}
Enter fullscreen mode Exit fullscreen mode

Failures accumulate until the circuit opens.


🚦 4. Blocking Requests When Circuit Is Open

Before performing a request:

func canExecute() -> Bool {
    switch state {
    case .closed:
        return true

    case .open(let until):
        return Date() > until

    case .halfOpen:
        return true
    }
}
Enter fullscreen mode Exit fullscreen mode

Usage:

guard breaker.canExecute() else {
    throw NetworkError.circuitOpen
}
Enter fullscreen mode Exit fullscreen mode

This protects the backend.


πŸ”„ 5. Transition to Half-Open

When timeout expires:

case .open(let until):
    if Date() > until {
        state = .halfOpen
        return true
    }
Enter fullscreen mode Exit fullscreen mode

Only a small number of requests should test recovery.

If they succeed β†’ close circuit.

If they fail β†’ reopen.


πŸ”‹ 6. Why Mobile Apps Need Circuit Breakers

Mobile networks are unstable.

Common failure scenarios:

  • server outage
  • DNS issues
  • TLS failures
  • rate limiting
  • cellular packet loss

Without circuit breakers:

  • your app spams the backend
  • battery drains rapidly
  • retries amplify the outage

Circuit breakers prevent this.


🧱 7. Integrating with API Clients

Wrap network calls:

func performRequest<T>(_ operation: () async throws -> T) async throws -> T {

    guard breaker.canExecute() else {
        throw NetworkError.circuitOpen
    }

    do {
        let result = try await operation()
        breaker.recordSuccess()
        return result
    } catch {
        breaker.recordFailure()
        throw error
    }
}
Enter fullscreen mode Exit fullscreen mode

This makes resilience automatic.


🌐 8. Combine with Retry Strategies

Circuit breakers work with retries.

Example flow:

Request
β†’ Failure
β†’ Retry (exponential backoff)
β†’ Failure threshold reached
β†’ Circuit opens
β†’ Requests paused
Enter fullscreen mode Exit fullscreen mode

This prevents retry storms.


πŸ§ͺ 9. Testing Circuit Breakers

Test scenarios:

  • repeated server errors
  • timeout storms
  • network disconnections
  • recovery after outage
  • half-open transition

Verify that:

  • requests stop during outages
  • requests resume after recovery

⚠️ 10. Common Anti-Patterns

Avoid:

  • infinite retries
  • retrying immediately after failure
  • ignoring server rate limits
  • retrying while offline
  • not tracking failure counts

These cause:

  • backend overload
  • cascading outages
  • battery drain

🧠 Mental Model

Think:

Request
 β†’ Failure Detection
   β†’ Circuit Breaker
     β†’ Pause Requests
       β†’ Recovery Probe
         β†’ Resume Traffic
Enter fullscreen mode Exit fullscreen mode

Not:

β€œJust retry again.”


πŸš€ Final Thoughts

Circuit breakers give your app:

  • backend protection
  • smarter retry behavior
  • reduced battery usage
  • resilience during outages
  • predictable recovery

This is the difference between:

  • a fragile network client
  • and a production-grade mobile system

Top comments (0)