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.
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()
);
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
}
}
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
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"
}
}
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
}
}
Invalid configuration data produces specific error responses:
{
"error": "validation_failed",
"message": "configuration.rollout_percentage must be between 0 and 1",
"field": "configuration.rollout_percentage"
}
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
}
}
Flags can target specific users or user groups:
{
"name": "feature-x",
"enabled": true,
"configuration": {
"target_users": ["user_123", "user_456"],
"target_groups": ["beta-testers"]
}
}
Flags can vary by environment:
{
"name": "feature-y",
"enabled": true,
"configuration": {
"environment": ["staging", "production"]
}
}
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"
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
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"
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"
}
}
Flag services expose health endpoints:
GET /health
Accept: application/json
Example response:
{
"status": "healthy",
"database": "connected",
"cache": "connected"
}
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"
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
}
}
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"
}
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.
Different environments may have different flag configurations without synchronization. This results in inconsistent user experiences.
An invalid cache entry can cause all requests to return stale values until expiration or manual refresh.
Concurrent updates to flags may result in lost updates if not properly handled with optimistic locking or versioning.
Applications default to enabled when a flag is missing, which differs from the expected disabled state.
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"}}'
# 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
Validate flag configurations:
curl -X GET http://flag-service/api/flags/validation \
-H "Accept: application/json"
{
"name": "minimal-flag",
"enabled": true
}
{
"name": "complex-feature",
"enabled": true,
"configuration": {
"rollout_percentage": 0.5,
"target_users": ["user_1", "user_2"],
"environment": ["staging", "production"],
"version": "2.0"
}
}
{
"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.
We use cookies for analytics (Google Analytics) and advertising (Google AdSense) to improve your experience and support free content. Privacy Policy