- Codechamps
- Posts
- 🚀 Rollout Control in Python: Build Your Own Feature Flag System
🚀 Rollout Control in Python: Build Your Own Feature Flag System
Learn how to build a flexible, user-targeted feature flag system using FastAPI, JSON storage, and rollout logic—perfect for safe deployments and A/B testing
🧠 What Are Feature Flags?
Feature flags (also called feature toggles) allow you to turn features on or off at runtime, without redeploying or changing your codebase. They give you fine-grained control over how and when features are exposed to users, making them a powerful tool in modern software delivery.
🧩 How They Work (At a High Level)
You wrap feature logic in a conditional check like this:
if feature_flag_enabled("some_feature"):
render_new_functionality()
else:
render_old_functionality()
Instead of deploying new code to change behavior, you can just toggle a value in your database, config file, or dashboard.
🔄 Real Use Cases
Use Case | Description |
---|---|
Gradual Rollouts | Release a feature to 5%, then 25%, then 100% of users |
A/B Testing | Show different versions to users and measure performance |
Kill Switch | Instantly disable a buggy or unstable feature |
Internal Testing | Let only your team or QA access certain functionality |
Customer-specific Features | Enable features only for beta testers or paying customers |
🔧 What We’re Building
✅ Features:
Create / Update / Read flags
In-memory + JSON file persistence
Boolean flags (
on
/off
)Percentage-based rollout
User-targeted flags
REST API to manage flags
📐 System Architecture
Here’s the architecture we’ll use:

🔲 Architecture Components:
FastAPI for REST endpoints
Flag Store abstraction (JSON file)
Rollout Engine (decides if a flag applies to a user)
🔧 Code Explanation: main.py
📄 File: main.py
This file contains the FastAPI web server that exposes the feature flag endpoints.
from fastapi import FastAPI, HTTPException
from flag_store import FlagStore
from rollout import RolloutEngine
FastAPI
: Web framework for building APIs in Python.HTTPException
: Used to return errors like 404 if a flag isn’t found.FlagStore
: Handles reading/writing flag data (from JSON or another backend).RolloutEngine
: Evaluates if a flag is "on" for a specific user.
✅ App and Instances
app = FastAPI()
store = FlagStore("flags.json")
engine = RolloutEngine(store)
FastAPI()
initialize the app.FlagStore("flags.json")
reads flag data from a file calledflags.json
.RolloutEngine
contains logic to evaluate if a flag should be active for a user, based on flag rules like rollout %.
📥 GET: Check Flag Status
@app.get("/flags/{flag_name}")
def get_flag(flag_name: str, user_id: str = None):
flag_result = engine.evaluate_flag(flag_name, user_id)
if flag_result is None:
raise HTTPException(status_code=404, detail="Flag not found")
return {"flag": flag_name, "enabled": flag_result}
GET /flags/<flag_name>
: Fetches whether a feature is enabled for a user.Accepts optional query param
user_id
for personalized evaluation.Calls
RolloutEngine.evaluate_flag()
to calculate if the flag is active.Returns
{ "flag": "new_ui", "enabled": true }
📝 POST: Set a Flag
@app.post("/flags/{flag_name}")
def set_flag(flag_name: str, enabled: bool, rollout_percentage: int = 100):
store.set_flag(flag_name, {"enabled": enabled, "rollout": rollout_percentage})
return {"status": "updated"}
POST /flags/<flag_name>
: Updates (or creates) a flag.Query parameters:
enabled
: whether the feature is on or off.rollout_percentage
: percentage of users to show the feature to (defaults to 100).
Stores the data in the flag store as a dictionary:
{ "enabled": true, "rollout": 50 }
📦 Code Explanation: flag_store.py
📄 File: flag_store.py
This file manages reading and writing flags. It simulates a key-value store like Redis or a database, using a local JSON file.
import json
from threading import Lock
json
: To serialize/deserialize flag data.Lock
: Ensures thread-safe access to the flag store (for concurrent writes).
🏗️ Class: FlagStore
class FlagStore:
def __init__(self, path):
self.path = path
self.lock = Lock()
path
: the path to a file (flags.json
) where flags are saved.lock
: ensures atomic writes (important if using this in multithreaded environments).
🔍 Load Flags on Startup
try:
with open(self.path, "r") as f:
self.flags = json.load(f)
except FileNotFoundError:
self.flags = {}
Tries to load existing flags from a file.
If the file doesn’t exist, starts with an empty dictionary.
💾 Save to Disk
def save(self):
with open(self.path, "w") as f:
json.dump(self.flags, f, indent=2)
Write the current state of all flags to the file in a pretty format.
🧠 Access Methods
def get_flag(self, name):
return self.flags.get(name)
Fetches a specific flag by name (e.g.,
"new_ui"
).
def set_flag(self, name, value: dict):
with self.lock:
self.flags[name] = value
self.save()
Updates or creates a flag entry.
Stores a dict-like
{ "enabled": true, "rollout": 50 }
Uses
lock
to prevent race conditions while writing.
🧠 Code Explanation: rollout.py
This module contains the logic to decide whether a user sees a feature.
📄 File: rollout.py
import hashlib
Uses hashing to assign users into “buckets” for percentage rollout.
🧠 Class: RolloutEngine
class RolloutEngine:
def __init__(self, store):
self.store = store
Stores a reference to the
FlagStore
, so it can access flag configs.
🔍 Flag Evaluation Logic
def evaluate_flag(self, name, user_id=None):
flag = self.store.get_flag(name)
if not flag:
return None
if not flag.get("enabled", False):
return False
Fetches the flag from the store.
If the flag doesn’t exist → returns
None
(not found).If the flag is disabled, → immediately returns
False
.
🎯 Percentage Rollout Logic
if not user_id:
return flag["enabled"]
If no
user_id
is given, just return the basic flag state.
bucket = int(hashlib.sha256(user_id.encode()).hexdigest(), 16) % 100
return bucket < flag.get("rollout", 100)
Hashes the
user_id
to get a deterministic number from 0–99.Compares it to the rollout percentage.
If the user falls within the bucket range (e.g., 0–49 for 50% rollout), the flag is active.
✅ This ensures the same user always gets the same flag state.
🧰 Complete code
A full example of a feature flag API using FastAPI, supporting boolean flags and gradual rollouts by user.
📁 Directory Structure
feature_flags/
├── main.py # FastAPI app
├── flag_store.py # Stores flags (JSON-based)
├── rollout.py # Handles rollout logic
└── flags.json # Stores flag data
1️⃣ main.py
from fastapi import FastAPI, HTTPException
from flag_store import FlagStore
from rollout import RolloutEngine
app = FastAPI()
store = FlagStore("flags.json")
engine = RolloutEngine(store)
@app.get("/flags/{flag_name}")
def get_flag(flag_name: str, user_id: str = None):
flag_result = engine.evaluate_flag(flag_name, user_id)
if flag_result is None:
raise HTTPException(status_code=404, detail="Flag not found")
return {"flag": flag_name, "enabled": flag_result}
@app.post("/flags/{flag_name}")
def set_flag(flag_name: str, enabled: bool, rollout_percentage: int = 100):
store.set_flag(flag_name, {"enabled": enabled, "rollout": rollout_percentage})
return {"status": "updated"}
2️⃣ flag_store.py
import json
from threading import Lock
class FlagStore:
def __init__(self, path):
self.path = path
self.lock = Lock()
try:
with open(self.path, "r") as f:
self.flags = json.load(f)
except FileNotFoundError:
self.flags = {}
def save(self):
with open(self.path, "w") as f:
json.dump(self.flags, f, indent=2)
def get_flag(self, name):
return self.flags.get(name)
def set_flag(self, name, value: dict):
with self.lock:
self.flags[name] = value
self.save()
3️⃣ rollout.py
import hashlib
class RolloutEngine:
def __init__(self, store):
self.store = store
def evaluate_flag(self, name, user_id=None):
flag = self.store.get_flag(name)
if not flag:
return None
if not flag.get("enabled", False):
return False
if not user_id:
return flag["enabled"]
bucket = int(hashlib.sha256(user_id.encode()).hexdigest(), 16) % 100
return bucket < flag.get("rollout", 100)
4️⃣ flags.json
(Example)
{
"some_feature": {
"enabled": true,
"rollout": 50
}
}
🚀 How to Run
Install dependencies:
pip install fastapi uvicorn
Run the server:
uvicorn main:app --reload
Example API calls:
GET /flags/new_ui?user_id=123
POST /flags/new_ui?enabled=true&rollout_percentage=25
💡 Recap
✅ You built a complete feature flag service
✅ Supports on/off, percentage rollout, and user targeting
✅ Uses clean architecture for scale
✅ Easy to integrate with any backend
✉️ Subscribe for more builds like this
📬 Join hundreds of devs mastering software engineering by building, breaking, and shipping real software.