← Back to Tutorials
Python

Build a Feature Flag System

Difficulty: Intermediate Est. Time: ~3 hours

Introduction

Feature flags (or feature toggles) are a powerful technique that allows you to enable or disable features without deploying new code. They enable continuous deployment, A/B testing, and gradual rollouts of new features.

In this tutorial, we'll build a complete feature flag system with targeting rules, percentage rollouts, and evaluation strategies.

What You'll Build
  • A feature flag store
  • Boolean and multivariate flags
  • Targeting rules based on user attributes
  • Percentage-based rollouts
  • Flag evaluation engine
What You'll Learn
  • How feature flags work
  • Targeting and segmentation
  • Gradual feature rollouts
  • Configuration-driven feature management

Core Concepts

Flag Types

Feature flags come in several forms:

  • Boolean flags - Simple on/off toggles
  • Multivariate flags - Multiple variants (e.g., A/B testing)
  • Percentage rollouts - Gradual enablement by percentage

Targeting

Targeting allows you to enable features for specific users based on attributes like email, region, or custom properties.

Project Overview

Our feature flag system will include:

Feature Description
Boolean Flags Simple on/off toggles
Targeting User attribute-based rules
Rollouts Percentage-based enablement
Caching In-memory flag caching

Prerequisites

  • Python 3.8+ - Installed on your system
  • Basic Python knowledge - Classes, decorators

Flag Store

Create featureflags/store.py:

from typing import Dict, List, Optional, Any
from dataclasses import dataclass, field
from enum import Enum
import json
import time


class FlagType(Enum):
    BOOLEAN = "boolean"
    MULTIVARIATE = "multivariate"
    PERCENTAGE = "percentage"


@dataclass
class TargetingRule:
    attribute: str
    operator: str
    values: List[Any]


@dataclass
class Flag:
    name: str
    flag_type: FlagType
    enabled: bool = False
    default_value: Any = False
    targeting_rules: List[TargetingRule] = field(default_factory=list)
    rollout_percentage: int = 0
    variants: Dict[str, Any] = field(default_factory=dict)


class FlagStore:
    def __init__(self):
        self._flags: Dict[str, Flag] = {}
        self._cache: Dict[str, Any] = {}
        self._cache_ttl = 60
    
    def create_flag(self, name: str, flag_type: FlagType = FlagType.BOOLEAN, 
                   default_value: Any = False) -> Flag:
        flag = Flag(name=name, flag_type=flag_type, default_value=default_value)
        self._flags[name] = flag
        self._invalidate_cache(name)
        return flag
    
    def get_flag(self, name: str) -> Optional[Flag]:
        return self._flags.get(name)
    
    def update_flag(self, flag: Flag):
        self._flags[flag.name] = flag
        self._invalidate_cache(flag.name)
    
    def delete_flag(self, name: str):
        self._flags.pop(name, None)
        self._invalidate_cache(name)
    
    def get_all_flags(self) -> List[Flag]:
        return list(self._flags.values())
    
    def _invalidate_cache(self, name: str):
        self._cache.pop(name, None)
    
    def load_from_file(self, filepath: str):
        with open(filepath, 'r') as f:
            data = json.load(f)
        
        for flag_data in data.get('flags', []):
            rules = []
            for rule_data in flag_data.get('targeting', []):
                rules.append(TargetingRule(
                    attribute=rule_data['attribute'],
                    operator=rule_data['operator'],
                    values=rule_data['values']
                ))
            
            flag = Flag(
                name=flag_data['name'],
                flag_type=FlagType(flag_data.get('type', 'boolean')),
                enabled=flag_data.get('enabled', False),
                default_value=flag_data.get('default', False),
                targeting_rules=rules,
                rollout_percentage=flag_data.get('rollout', 0),
                variants=flag_data.get('variants', {})
            )
            self._flags[flag.name] = flag
    
    def save_to_file(self, filepath: str):
        flags_data = []
        
        for flag in self._flags.values():
            flag_dict = {
                'name': flag.name,
                'type': flag.flag_type.value,
                'enabled': flag.enabled,
                'default': flag.default_value,
                'rollout': flag.rollout_percentage,
                'variants': flag.variants,
                'targeting': [
                    {
                        'attribute': rule.attribute,
                        'operator': rule.operator,
                        'values': rule.values
                    }
                    for rule in flag.targeting_rules
                ]
            }
            flags_data.append(flag_dict)
        
        with open(filepath, 'w') as f:
            json.dump({'flags': flags_data}, f, indent=2)

Flag Evaluation

Create featureflags/evaluator.py:

import hashlib
from typing import Dict, Any, Optional
from .store import FlagStore, Flag, FlagType


class EvaluationContext:
    def __init__(self, user_id: str = None, attributes: Dict[str, Any] = None):
        self.user_id = user_id
        self.attributes = attributes or {}


class FlagEvaluator:
    def __init__(self, store: FlagStore):
        self.store = store
    
    def evaluate(self, flag_name: str, context: EvaluationContext, 
                 default: Any = None) -> Any:
        flag = self.store.get_flag(flag_name)
        
        if not flag:
            return default
        
        if not flag.enabled:
            return flag.default_value
        
        if flag.targeting_rules:
            if self._evaluate_targeting(flag, context):
                return self._evaluate_flag(flag, context)
        
        if flag.rollout_percentage > 0 and context.user_id:
            if self._evaluate_rollout(flag, context):
                return self._evaluate_flag(flag, context)
        
        if flag.rollout_percentage == 100:
            return self._evaluate_flag(flag, context)
        
        return flag.default_value
    
    def _evaluate_targeting(self, flag: Flag, context: EvaluationContext) -> bool:
        for rule in flag.targeting_rules:
            value = context.attributes.get(rule.attribute)
            
            if rule.operator == 'eq':
                if value == rule.values[0]:
                    return True
            elif rule.operator == 'neq':
                if value != rule.values[0]:
                    return True
            elif rule.operator == 'in':
                if value in rule.values:
                    return True
            elif rule.operator == 'contains':
                if value and any(v in str(value) for v in rule.values):
                    return True
        
        return False
    
    def _evaluate_rollout(self, flag: Flag, context: EvaluationContext) -> bool:
        hash_value = self._hash_for_user(flag.name, context.user_id)
        bucket = hash_value % 100
        return bucket < flag.rollout_percentage
    
    def _evaluate_flag(self, flag: Flag, context: EvaluationContext) -> Any:
        if flag.flag_type == FlagType.BOOLEAN:
            return True
        elif flag.flag_type == FlagType.MULTIVARIATE:
            return self._select_variant(flag, context)
        return flag.default_value
    
    def _select_variant(self, flag: Flag, context: EvaluationContext) -> str:
        hash_value = self._hash_for_user(flag.name + '_variant', context.user_id)
        bucket = hash_value % 100
        
        cumulative = 0
        for variant, percentage in flag.variants.items():
            cumulative += percentage
            if bucket < cumulative:
                return variant
        
        return list(flag.variants.keys())[0] if flag.variants else flag.default_value
    
    def _hash_for_user(self, flag_name: str, user_id: str) -> int:
        key = f"{flag_name}:{user_id}"
        hash_bytes = hashlib.md5(key.encode()).digest()
        return int.from_bytes(hash_bytes[:2], 'big')
    
    def is_enabled(self, flag_name: str, context: EvaluationContext = None) -> bool:
        context = context or EvaluationContext()
        result = self.evaluate(flag_name, context, False)
        return bool(result)

Targeting Rules

Add advanced targeting capabilities:

# Create flags with targeting
store = FlagStore()

# Boolean flag with targeting
dark_mode = store.create_flag('dark-mode', FlagType.BOOLEAN)
dark_mode.enabled = True
dark_mode.targeting_rules = [
    TargetingRule(attribute='region', operator='in', values=['US', 'EU']),
    TargetingRule(attribute='tier', operator='eq', values=['premium']),
]
store.update_flag(dark_mode)

# Percentage rollout
new_checkout = store.create_flag('new-checkout', FlagType.BOOLEAN)
new_checkout.enabled = True
new_checkout.rollout_percentage = 10  # 10% of users
store.update_flag(new_checkout)

# Multivariate flag for A/B testing
button_color = store.create_flag('button-color', FlagType.MULTIVARIATE)
button_color.enabled = True
button_color.variants = {'blue': 50, 'green': 30, 'red': 20}
button_color.rollout_percentage = 100
store.update_flag(button_color)

# Save configuration
store.save_to_file('flags.json')

Testing the System

from featureflags import FlagStore, FlagEvaluator, EvaluationContext, FlagType

store = FlagStore()
store.load_from_file('flags.json')

evaluator = FlagEvaluator(store)

# Test basic evaluation
context = EvaluationContext(user_id='user123', attributes={'tier': 'premium'})
result = evaluator.evaluate('dark-mode', context, False)
print(f"Dark mode enabled: {result}")

# Test percentage rollout
context1 = EvaluationContext(user_id='user001')
context2 = EvaluationContext(user_id='user999')

result1 = evaluator.is_enabled('new-checkout', context1)
result2 = evaluator.is_enabled('new-checkout', context2)
print(f"User 001: {result1}, User 999: {result2}")

# Test multivariate
variant = evaluator.evaluate('button-color', context1, 'blue')
print(f"Button variant: {variant}")

Summary

Congratulations! You've built a complete feature flag system. Here's what you learned:

  • Flag Store - How to store and manage feature flags
  • Flag Evaluation - How to evaluate flags with context
  • Targeting Rules - How to create user-based targeting
  • Rollouts - How to implement percentage-based rollouts

Possible Extensions

  • Add real-time flag updates via webhooks
  • Implement flag analytics and metrics
  • Add flag expiration dates
  • Implement flag dependencies