Feature Flags
Manage feature flags to control feature availability and enable gradual rollouts.
Overview
Feature flags (also called feature toggles) allow administrators to:
- Enable/disable features without code deployment
- Roll out features gradually to user segments
- Test features with specific users
- Quick rollback if issues arise
- A/B test new functionality
Current Implementation Status
Note: BookWish does not currently have a formal feature flag system implemented. This guide outlines best practices and a recommended approach for when the system is implemented.
Feature Flag Concepts
Types of Feature Flags
Release Flags
- Control new feature rollouts
- Long-lived (weeks to months)
- Removed after full rollout
- Example: New recommendation algorithm
Experiment Flags
- A/B testing and experiments
- Medium-lived (days to weeks)
- Track metrics and compare variants
- Example: New UI layout test
Ops Flags
- Control operational aspects
- Long-lived (permanent)
- System health and performance
- Example: Enable/disable caching
Permission Flags
- Control access to premium/admin features
- Permanent or long-lived
- Based on user tier/permissions
- Example: Premium-only features
Flag Targeting
User-Based:
- Specific user IDs
- User tier (free, premium, bookstore, admin)
- User attributes (location, signup date)
Percentage-Based:
- Gradual rollout (10%, 25%, 50%, 100%)
- Canary deployments
- Progressive enhancement
Context-Based:
- Environment (dev, staging, production)
- Platform (web, mobile, iOS, Android)
- Geographic location
Recommended Implementation
Database Schema
CREATE TABLE feature_flags (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(255) UNIQUE NOT NULL,
description TEXT,
enabled BOOLEAN DEFAULT false,
rollout_percentage INTEGER DEFAULT 0, -- 0-100
enabled_tiers TEXT[], -- ['premium', 'admin']
enabled_user_ids TEXT[], -- Specific users
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
CREATE INDEX idx_feature_flags_enabled ON feature_flags(enabled);
CREATE INDEX idx_feature_flags_name ON feature_flags(name);
Backend Implementation
// Feature flag service
class FeatureFlagService {
async isEnabled(
flagName: string,
context: {
userId?: string;
userTier?: UserTier;
randomValue?: number; // 0-100 for percentage rollout
}
): Promise<boolean> {
const flag = await this.getFlag(flagName);
if (!flag || !flag.enabled) {
return false;
}
// Check user tier
if (flag.enabledTiers?.length > 0) {
if (!context.userTier || !flag.enabledTiers.includes(context.userTier)) {
return false;
}
}
// Check specific user IDs
if (flag.enabledUserIds?.length > 0) {
if (!context.userId || !flag.enabledUserIds.includes(context.userId)) {
return false;
}
}
// Check percentage rollout
if (flag.rolloutPercentage < 100) {
const randomValue = context.randomValue ?? this.getUserHash(context.userId);
return randomValue < flag.rolloutPercentage;
}
return true;
}
private getUserHash(userId?: string): number {
// Consistent hash for user (0-100)
if (!userId) return Math.random() * 100;
// Use consistent hashing algorithm
return (hashCode(userId) % 100 + 100) % 100;
}
}
Frontend Implementation
// React hook for feature flags
function useFeatureFlag(flagName: string): boolean {
const [enabled, setEnabled] = useState(false);
const user = useUser();
useEffect(() => {
async function checkFlag() {
const result = await api.get(`/api/feature-flags/${flagName}`, {
userId: user?.id,
userTier: user?.tier
});
setEnabled(result.enabled);
}
checkFlag();
}, [flagName, user]);
return enabled;
}
// Usage in components
function NewFeatureComponent() {
const newAlgorithmEnabled = useFeatureFlag('new-recommendation-algorithm');
if (newAlgorithmEnabled) {
return <NewRecommendations />;
}
return <OldRecommendations />;
}
Managing Feature Flags
Creating Feature Flags
Create New Flag:
INSERT INTO feature_flags (name, description, enabled, rollout_percentage)
VALUES (
'new-search-ui',
'New search interface with filters and facets',
false,
0
);
Flag Naming Conventions:
- Use kebab-case:
new-search-ui - Be descriptive:
enhanced-recommendation-algorithm - Include context:
mobile-app-dark-mode
Enabling Feature Flags
Enable for All Users:
UPDATE feature_flags
SET enabled = true,
rollout_percentage = 100
WHERE name = 'feature-name';
Enable for Specific Tiers:
UPDATE feature_flags
SET enabled = true,
enabled_tiers = ARRAY['premium', 'admin']
WHERE name = 'premium-feature';
Enable for Specific Users:
UPDATE feature_flags
SET enabled = true,
enabled_user_ids = ARRAY['user-id-1', 'user-id-2']
WHERE name = 'beta-feature';
Gradual Rollout:
-- Start with 10% of users
UPDATE feature_flags
SET enabled = true,
rollout_percentage = 10
WHERE name = 'new-feature';
-- Increase to 25%
UPDATE feature_flags
SET rollout_percentage = 25
WHERE name = 'new-feature';
-- Full rollout
UPDATE feature_flags
SET rollout_percentage = 100
WHERE name = 'new-feature';
Disabling Feature Flags
Quick Disable (Rollback):
UPDATE feature_flags
SET enabled = false
WHERE name = 'problematic-feature';
Reduce Rollout:
UPDATE feature_flags
SET rollout_percentage = 10 -- Back to 10%
WHERE name = 'buggy-feature';
Removing Feature Flags
After Full Rollout:
- Feature fully deployed and stable
- Remove flag checks from code
- Deploy code changes
- Delete flag from database
DELETE FROM feature_flags
WHERE name = 'old-completed-flag';
Feature Flag Strategies
Gradual Rollout Strategy
Day 1: Internal Testing
- Enable for admin users only
- Test thoroughly
- Fix critical issues
Day 3: Canary (5%)
- Enable for 5% of users
- Monitor metrics closely
- Watch for errors/complaints
Day 7: Expanded (25%)
- Increase to 25% if stable
- Continue monitoring
- Address feedback
Day 14: Majority (75%)
- Increase to 75%
- Most users have access
- Fine-tune based on data
Day 21: Full Rollout (100%)
- Enable for all users
- Monitor for any issues
- Plan flag removal
A/B Testing Strategy
Setup:
- Create two feature flags (variant A, variant B)
- Enable each for 50% of users (different users)
- Track metrics for both groups
- Compare results
Example:
-- Variant A: New algorithm
INSERT INTO feature_flags (name, enabled, rollout_percentage)
VALUES ('recommendation-algorithm-v2', true, 50);
-- Variant B: Old algorithm (everyone not in v2)
-- Implemented in code logic
Analysis:
- Track conversion, engagement, retention
- Statistical significance testing
- Make data-driven decision
- Roll out winner to 100%
Kill Switch Strategy
Emergency Disable:
For features that might cause issues:
- Deploy with flag disabled
- Enable gradually
- Keep flag permanently for quick disable
- Monitor closely
Use Cases:
- Resource-intensive features
- External API integrations
- Experimental algorithms
- High-risk changes
Feature Flag Best Practices
Flag Hygiene
Remove Old Flags:
- Don't accumulate flags indefinitely
- Set removal date when creating
- Track flag age
- Clean up quarterly
Document Flags:
- Clear description
- Owner/team responsible
- Creation date and reason
- Removal timeline
Testing
Test Both Paths:
- Code with flag enabled
- Code with flag disabled
- Edge cases and transitions
- Flag changes during user session
Monitor Closely:
- Error rates
- Performance metrics
- User feedback
- System health
Communication
Inform Team:
- Announce new flags
- Share rollout plan
- Document flag purpose
- Coordinate with stakeholders
User Communication:
- Beta tester notifications
- Feature announcements
- Feedback channels
- Clear expectations
Feature Flag Use Cases
BookWish Examples
New Recommendation Algorithm
INSERT INTO feature_flags (name, description, enabled, rollout_percentage)
VALUES (
'smart-recommendations',
'ML-powered book recommendations based on user behavior',
true,
25 -- Start with 25% rollout
);
Premium Feature Gate
INSERT INTO feature_flags (name, description, enabled, enabled_tiers)
VALUES (
'unlimited-wishlists',
'Remove wishlist limit for premium users',
true,
ARRAY['premium', 'admin']
);
Mobile App Dark Mode
INSERT INTO feature_flags (name, description, enabled)
VALUES (
'mobile-dark-mode',
'Dark mode theme for mobile app',
true
);
Store Custom Domains
INSERT INTO feature_flags (name, description, enabled, enabled_tiers)
VALUES (
'store-custom-domains',
'Allow stores to use custom domains',
true,
ARRAY['bookstore', 'admin']
);
Monitoring Feature Flags
Flag Usage Analytics
Track Flag Evaluations:
CREATE TABLE feature_flag_evaluations (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
flag_name VARCHAR(255) NOT NULL,
user_id VARCHAR(255),
user_tier VARCHAR(50),
enabled BOOLEAN,
created_at TIMESTAMP DEFAULT NOW()
);
Query Flag Usage:
SELECT flag_name,
COUNT(*) as evaluations,
COUNT(CASE WHEN enabled THEN 1 END) as enabled_count,
COUNT(DISTINCT user_id) as unique_users
FROM feature_flag_evaluations
WHERE created_at >= NOW() - INTERVAL '7 days'
GROUP BY flag_name;
Flag Impact Metrics
Compare Metrics:
- Conversion rates (enabled vs. disabled)
- Engagement metrics
- Error rates
- Performance metrics
- User satisfaction
Admin API Endpoints
Proposed Endpoints
List All Flags:
GET /api/admin/feature-flags
Authorization: Bearer <admin_token>
Get Flag Details:
GET /api/admin/feature-flags/:name
Authorization: Bearer <admin_token>
Create Flag:
POST /api/admin/feature-flags
Authorization: Bearer <admin_token>
Content-Type: application/json
{
"name": "new-feature",
"description": "Description of feature",
"enabled": false,
"rolloutPercentage": 0
}
Update Flag:
PUT /api/admin/feature-flags/:name
Authorization: Bearer <admin_token>
Content-Type: application/json
{
"enabled": true,
"rolloutPercentage": 50
}
Delete Flag:
DELETE /api/admin/feature-flags/:name
Authorization: Bearer <admin_token>
Third-Party Solutions
As an alternative to building a custom system, consider:
LaunchDarkly
- Enterprise feature flag platform
- Rich targeting and segmentation
- Analytics and insights
- SDK for all platforms
Split.io
- Feature flags with experimentation
- Real-time metrics
- User segmentation
- CI/CD integration
Flagsmith
- Open-source option
- Self-hosted or cloud
- Multi-environment support
- Simple interface
ConfigCat
- Developer-friendly
- Affordable pricing
- Fast response times
- Good documentation