Feature Flag Architectures

Design feature flag systems that enable safe, controlled feature rollouts. Covers flag types, targeting, evaluation, lifecycle management, technical debt, and the patterns that make feature flags a superpower instead of a liability.

Feature flags enable conditional code execution without requiring full deployments, allowing teams to toggle functionality in production. This approach matters when releasing features incrementally, managing risk, or supporting A/B testing.

Implementing a Feature Flag System

Basic Flag Structure and Storage

A feature flag typically consists of:

The simplest implementation uses a key-value store. Example SQL schema:

CREATE TABLE feature_flags (
    name VARCHAR(255) PRIMARY KEY,
    enabled BOOLEAN NOT NULL DEFAULT FALSE,
    configuration JSONB,
    created_at TIMESTAMP NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMP NOT NULL DEFAULT NOW()
);

Reading Flags in Application Code

Applications query flag state through an API endpoint or local cache. Example HTTP request:

GET /api/flags/user-profile?user_id=12345
Accept: application/json

Response format:

{
  "name": "user-profile",
  "enabled": true,
  "configuration": {
    "version": "v2",
    "timeout_ms": 5000
  }
}

Flag Evaluation Logic

The system evaluates flags based on user context, environment, or rollout strategy. Example evaluation function:

def is_flag_enabled(flag_name, user_context):
    flag = get_flag_from_storage(flag_name)
    if not flag.enabled:
        return False
    
    # Apply targeting rules
    if 'target_users' in flag.configuration:
        if user_context.get('user_id') not in flag.configuration['target_users']:
            return False
    
    return True

Managing Flag Configuration

Versioning Flags with Metadata

Flags may include version information and metadata. The configuration field accepts arbitrary JSON:

{
  "name": "new-login-flow",
  "enabled": true,
  "configuration": {
    "version": "2.1",
    "rollout_percentage": 0.1,
    "target_users": ["user_1", "user_2"],
    "environment": ["staging", "production"]
  },
  "metadata": {
    "owner": "frontend-team",
    "created_by": "alice",
    "last_modified": "2023-05-15T14:30:00Z"
  }
}

Updating Flags

Update a flag using HTTP PATCH:

PATCH /api/flags/new-login-flow
Content-Type: application/json
Accept: application/json

{
  "enabled": true,
  "configuration": {
    "rollout_percentage": 0.25
  }
}

Configuration Validation

Invalid configuration data produces specific error responses:

{
  "error": "validation_failed",
  "message": "configuration.rollout_percentage must be between 0 and 1",
  "field": "configuration.rollout_percentage"
}

Rollout Strategies

Percentage-Based Rollouts

A flag can enable functionality for a percentage of users. The system uses consistent hashing or random selection to determine eligibility.

Example configuration:

{
  "name": "beta-feature",
  "enabled": true,
  "configuration": {
    "rollout_percentage": 0.3
  }
}

User Targeting

Flags can target specific users or user groups:

{
  "name": "feature-x",
  "enabled": true,
  "configuration": {
    "target_users": ["user_123", "user_456"],
    "target_groups": ["beta-testers"]
  }
}

Environment-Specific Flags

Flags can vary by environment:

{
  "name": "feature-y",
  "enabled": true,
  "configuration": {
    "environment": ["staging", "production"]
  }
}

Integration with Deployment Tools

Reading Flags in CI/CD Pipelines

Pipelines query flag status before deploying. Example command:

curl -s http://flag-service/api/flags/feature-xyz \
  -H "Accept: application/json" \
  -H "Authorization: Bearer $FLAG_SERVICE_TOKEN"

Conditional Deployment Based on Flags

A deployment script checks flag state:

#!/bin/bash
FLAG_STATUS=$(curl -s http://flag-service/api/flags/new-ui \
  -H "Accept: application/json" \
  -H "Authorization: Bearer $TOKEN")

if [ "$(echo "$FLAG_STATUS" | jq -r '.enabled')" = "true" ]; then
    echo "Deploying new UI"
    # deployment steps
else
    echo "Using legacy UI"
    # alternative deployment steps
fi

Flag Synchronization During Deployments

During a rollout, the system synchronizes flag state across services. Example synchronization command:

kubectl exec -it flag-service-7b5c8f9d4-xyz12 -- \
  curl -X POST http://localhost:8080/sync \
  -H "Content-Type: application/json"

Monitoring and Observability

Flag Usage Tracking

Applications log flag evaluations:

{
  "timestamp": "2023-05-15T14:30:00Z",
  "flag_name": "new-login-flow",
  "user_id": "user_123",
  "enabled": true,
  "evaluation_context": {
    "environment": "production"
  }
}

Health Checks

Flag services expose health endpoints:

GET /health
Accept: application/json

Example response:

{
  "status": "healthy",
  "database": "connected",
  "cache": "connected"
}

Production Considerations

Cache Invalidation and Consistency

The system maintains a cache of flag states. When flags change, the cache must be invalidated:

curl -X POST http://flag-service/api/invalidate-cache \
  -H "Authorization: Bearer $TOKEN"

Flag Service Outages

When the flag service is unavailable, applications may fall back to default behavior or cached values. Example fallback configuration:

{
  "name": "feature-a",
  "enabled": false,
  "fallback_enabled": true,
  "configuration": {
    "timeout_ms": 1000
  }
}

Error Handling

Failed flag requests return specific error codes:

GET /api/flags/nonexistent-flag
Accept: application/json

Response:

{
  "error": "flag_not_found",
  "message": "Feature flag 'nonexistent-flag' does not exist"
}

Common Pitfalls and Failure Modes

Silent Flag Failures

A misconfigured flag returns no error but behaves incorrectly:

{
  "name": "feature-b",
  "enabled": true,
  "configuration": {
    "rollout_percentage": -1.0
  }
}

This causes all users to be excluded from the feature.

Configuration Drift

Different environments may have different flag configurations without synchronization. This results in inconsistent user experiences.

Cache Poisoning

An invalid cache entry can cause all requests to return stale values until expiration or manual refresh.

Race Conditions

Concurrent updates to flags may result in lost updates if not properly handled with optimistic locking or versioning.

Default Behavior Confusion

Applications default to enabled when a flag is missing, which differs from the expected disabled state.

Operational Commands

Flag Management Interface

Example management interface commands:

# List all flags
curl http://flag-service/api/flags

# Get specific flag
curl http://flag-service/api/flags/my-feature

# Create new flag
curl -X POST http://flag-service/api/flags \
  -H "Content-Type: application/json" \
  -d '{"name": "new-feature", "enabled": false}'

# Update flag
curl -X PUT http://flag-service/api/flags/new-feature \
  -H "Content-Type: application/json" \
  -d '{"enabled": true, "configuration": {"version": "1.0"}}'

Health and Diagnostics

# Check service health
curl http://flag-service/health

# Get service metrics
curl http://flag-service/metrics

# Force cache refresh
curl -X POST http://flag-service/api/refresh-cache

Flag State Validation

Validate flag configurations:

curl -X GET http://flag-service/api/flags/validation \
  -H "Accept: application/json"

Configuration Examples

Minimal Flag Configuration

{
  "name": "minimal-flag",
  "enabled": true
}

Complex Configuration with Rollout

{
  "name": "complex-feature",
  "enabled": true,
  "configuration": {
    "rollout_percentage": 0.5,
    "target_users": ["user_1", "user_2"],
    "environment": ["staging", "production"],
    "version": "2.0"
  }
}

Flag with Error Handling

{
  "name": "error-handling-flag",
  "enabled": true,
  "configuration": {
    "fallback_enabled": true,
    "timeout_ms": 5000
  }
}

The architecture supports multiple strategies for feature management while maintaining clear separation between flag definition, evaluation logic, and deployment workflows.

This page was rewritten on 15 October 2026. It replaced a templated version that was not specific to its own title. How these pages are written and checked. If anything here is wrong, tell us at [email protected] and we will correct it.