Smart Targeting: Why User Segmentation is Your Secret Weapon

Sarah Chen
··
Feature FlagsTutorials
Smart Targeting: Why User Segmentation is Your Secret Weapon

Turning a feature on or off for everyone is easy. The real power of feature flags comes when you can target specific users, teams, or environments. This is where target lists transform feature flags from simple toggles into sophisticated deployment and experimentation tools.

What is User Targeting?

User targeting means controlling feature visibility based on user attributes. Instead of "everyone sees this" or "no one sees this," you get granular control:

  • Internal team members see new features first
  • Beta users get early access to test features
  • Enterprise customers get premium features
  • Users in specific regions see localized features
  • Mobile users get mobile-optimized experiences
  • Free tier users see upgrade prompts

This level of control changes everything about how you deploy and test software.

A beam directed toward selected groups of nodes in a larger network, illustrating targeted feature delivery to specific user segments.

Why Targeting Matters

1. Safe Testing in Production

The most powerful test environment isn't staging - it's production. But you need to limit blast radius:

// Enable new checkout flow only for internal team
const targetList = {
  feature: 'new-checkout-v2',
  enabled: true,
  targeting: {
    userEmails: [
      '@company.com', // All company emails
      'beta-tester@example.com',
    ],
  },
}

Your team tests with real production data, real payment processors, real third-party APIs - but customers never see the feature until it's ready.

2. Gradual Rollouts with Precision

Percentage-based rollouts are great, but sometimes you need more control:

Scenario: Launch a new recommendation algorithm

// Phase 1: Internal team (1 week)
targeting: { userIds: ['team-member-ids'] }

// Phase 2: Power users (1 week)
targeting: {
  userSegments: ['power-users'],
  minAccountAge: 90 // days
}

// Phase 3: Geographic rollout (2 weeks)
targeting: {
  regions: ['US-West', 'US-East', 'EU', 'APAC']
  // Roll out region by region
}

// Phase 4: Everyone
targeting: { enabled: true }

Each phase validates the feature with a specific group before expanding. If problems arise, they're contained.

3. Environment-Specific Features

Different environments need different features:

const featureConfig = {
  'debug-panel': {
    development: true,
    staging: true,
    production: false, // Never in prod
  },
  analytics: {
    development: false, // Don't pollute analytics
    staging: true,
    production: true,
  },
  'payment-sandbox': {
    development: true,
    staging: true,
    production: false, // Real payments only in prod
  },
}

This prevents development-only features from reaching production and ensures production-only features (like real payment processing) stay there.

The Anatomy of a Target List

A robust targeting system needs these capabilities:

User Identifiers

  • User IDs: Target specific users
  • Email patterns: *@company.com for internal teams
  • User segments: Beta users, premium customers, trial users

User Attributes

  • Account age: New vs returning customers
  • Subscription tier: Free, Pro, Enterprise
  • Feature usage: Active vs dormant users
  • Geographic location: Country, region, timezone

Device & Platform

  • Platform: Web, iOS, Android
  • Browser: Chrome, Safari, Firefox
  • Device type: Mobile, tablet, desktop
  • OS version: iOS 15+, Android 12+

Business Logic

  • Companies: B2B features for specific customers
  • Account value: High-value vs standard customers
  • Support tier: Premium support customers
  • Contract terms: Early access for specific agreements

Real-World Targeting Patterns

Pattern 1: The Beta Program

const betaFeatureConfig = {
  targeting: {
    // Beta opt-ins
    userSegments: ['beta-opted-in'],

    // OR internal team members
    emailPatterns: ['*@company.com'],

    // OR specific VIP users
    userIds: ['vip-customer-1', 'vip-customer-2'],
  },
}

This gives you three ways to access: explicit opt-in, being on the team, or being a VIP customer.

Pattern 2: The Premium Feature

const premiumFeatureConfig = {
  targeting: {
    subscriptionTiers: ['pro', 'enterprise'],

    // Grace period for recently downgraded users
    customRule: (user) => {
      if (user.tier === 'free' && user.recentlyDowngraded) {
        return user.downgradedDate > Date.now() - 7 * 24 * 60 * 60 * 1000
      }
      return false
    },
  },
}

Pro and Enterprise users always get access. Recently downgraded users get a 7-day grace period - good for retention.

Pattern 3: The Regional Rollout

const regionalFeatureConfig = {
  targeting: {
    // Stage 1: Australia (small, English-speaking market)
    stage: 1,
    countries: ['AU', 'NZ'],

    // Stage 2: Expand to US/UK after 1 week
    // stage: 2,
    // countries: ['AU', 'NZ', 'US', 'GB'],

    // Stage 3: Europe
    // stage: 3,
    // countries: ['AU', 'NZ', 'US', 'GB', 'DE', 'FR', 'ES', 'IT'],

    // Stage 4: Everywhere
    // stage: 4,
    // enabled: true
  },
}

Test in a small English-speaking market first, then expand systematically.

Pattern 4: The Mobile-First Feature

const mobileFeatureConfig = {
  targeting: {
    platforms: ['ios', 'android'],

    // Minimum versions that support the feature
    minVersions: {
      ios: '15.0',
      android: '12.0',
    },

    // Gradual rollout on mobile
    percentage: 50, // 50% of mobile users
  },
}

Launch on mobile where the feature works best, validate, then build desktop version.

Implementing Target Lists

Basic Implementation

interface TargetingRule {
  userIds?: string[]
  emailPatterns?: string[]
  userSegments?: string[]
  countries?: string[]
  subscriptionTiers?: string[]
  percentage?: number
  customRule?: (user: User) => boolean
}

async function evaluateTargeting(user: User, rules: TargetingRule): Promise<boolean> {
  // Check user IDs
  if (rules.userIds && rules.userIds.includes(user.id)) {
    return true
  }

  // Check email patterns
  if (rules.emailPatterns) {
    for (const pattern of rules.emailPatterns) {
      if (matchesPattern(user.email, pattern)) {
        return true
      }
    }
  }

  // Check user segments
  if (rules.userSegments) {
    const userSegments = await getUserSegments(user.id)
    if (userSegments.some((s) => rules.userSegments!.includes(s))) {
      return true
    }
  }

  // Check geographic location
  if (rules.countries && rules.countries.includes(user.country)) {
    return true
  }

  // Check subscription tier
  if (rules.subscriptionTiers && rules.subscriptionTiers.includes(user.tier)) {
    return true
  }

  // Percentage-based rollout
  if (rules.percentage) {
    const hash = hashUser(user.id)
    if (hash % 100 < rules.percentage) {
      return true
    }
  }

  // Custom rules
  if (rules.customRule) {
    return rules.customRule(user)
  }

  return false
}

Advanced: Multi-Environment Targeting

interface EnvironmentConfig {
  development: TargetingRule
  staging: TargetingRule
  production: TargetingRule
}

const multiEnvFeature: EnvironmentConfig = {
  development: {
    // Everyone in dev
    enabled: true,
  },
  staging: {
    // Team + beta users in staging
    emailPatterns: ['*@company.com'],
    userSegments: ['beta-testers'],
  },
  production: {
    // Gradual rollout in production
    percentage: 10,
    countries: ['US'], // US only for now
    subscriptionTiers: ['pro', 'enterprise'], // Premium only
  },
}

Common Pitfalls and Solutions

Pitfall 1: Targeting Rules Too Complex

Problem: Rules become unreadable and hard to debug

// Bad: Nested complexity
if (
  (user.tier === 'pro' || user.tier === 'enterprise') &&
  (user.country === 'US' || user.country === 'CA') &&
  (user.accountAge > 30 || user.revenueTotal > 1000) &&
  !user.hasOptedOut &&
  user.emailVerified
) {
  // When does this run? Hard to tell!
}

Solution: Use named segments

// Good: Named, testable segments
const segments = {
  'premium-customers': (u) => ['pro', 'enterprise'].includes(u.tier),
  'north-america': (u) => ['US', 'CA'].includes(u.country),
  'established-users': (u) => u.accountAge > 30 || u.revenueTotal > 1000,
  'eligible-users': (u) => !u.hasOptedOut && u.emailVerified,
}

if (
  isInSegments(user, ['premium-customers', 'north-america', 'established-users', 'eligible-users'])
) {
  // Clear and testable
}

Pitfall 2: Inconsistent User Assignment

Problem: Same user gets different features on different devices

// Bad: Random percentage each time
if (Math.random() < 0.5) {
  return true
}

Solution: Hash-based consistent assignment

// Good: Same user always gets same result
function isInPercentage(userId: string, percentage: number): boolean {
  const hash = hashFunction(userId)
  return hash % 100 < percentage
}

Pitfall 3: Target Lists That Never Get Cleaned Up

Problem: "Temporary" target lists become permanent

// Bad: Forever in the codebase
if (userIds.includes('old-beta-tester-from-2019')) {
  return true
}

Solution: Time-bound targeting with automatic expiration

// Good: Expires automatically
const betaConfig = {
  targeting: {
    userSegments: ['beta-testers'],
    expiresAt: '2024-12-31', // Automatic cleanup
  },
}

Best Practices

1. Start Narrow, Expand Gradually

Begin with the smallest possible group:

  1. Your team (10 people)
  2. Beta users (100 people)
  3. 1% of users (1,000 people)
  4. 10% of users (10,000 people)
  5. Everyone (100,000+ people)

Each step validates the feature before the next expansion.

2. Document Targeting Intent

const targetingConfig = {
  feature: 'new-dashboard',
  intent: 'Phase 2: Beta users in North America',
  reasoning: 'Validating with English-speaking users before localization',
  nextStep: 'Expand to EU after translation',
  owner: 'product-team@company.com',
  targeting: {
    userSegments: ['beta-opted-in'],
    countries: ['US', 'CA'],
  },
}

Future you will appreciate knowing why these targeting rules exist.

3. Monitor Segment Health

Track metrics per segment:

  • Error rates: Is one segment experiencing more errors?
  • Performance: Does the feature perform differently per segment?
  • Engagement: Are targeted users actually using the feature?
  • Conversion: Does the feature improve outcomes for the segment?

4. Provide Override Mechanisms

// Allow support team to enable features for specific users
if (adminOverride.enabled && adminOverride.userId === user.id) {
  return true
}

// Allow users to opt-out
if (userPreferences.optedOut.includes(featureId)) {
  return false
}

Sometimes you need to override targeting logic. Build escape hatches.

Targeting + Other Flag Patterns

Targeting becomes even more powerful when combined with other patterns:

Targeting + Kill Switches

const featureConfig = {
  killSwitch: false, // Override everything if true
  targeting: {
    // Normal targeting rules
  },
}

if (featureConfig.killSwitch) {
  return false // Feature disabled for everyone
}

Targeting + Percentage Rollouts

const featureConfig = {
  targeting: {
    userSegments: ['premium-customers'], // Only premium customers
    percentage: 25, // 25% of premium customers
  },
}

Targeting + A/B Tests

const abTestConfig = {
  targeting: {
    userSegments: ['web-users'], // Only web users in test
  },
  variants: {
    control: 50,
    treatment: 50,
  },
}

The Future: AI-Powered Targeting

Advanced systems are moving toward predictive targeting:

const aiTargeting = {
  objective: 'maximize-conversion',
  learnFrom: ['past-feature-adoptions', 'user-behavior-patterns'],
  autoTarget: {
    mostLikely: 'to-adopt',
    leastLikely: 'to-churn',
    highestValue: 'potential-revenue',
  },
}

Machine learning identifies which users are most likely to benefit from new features and targets them automatically.

Conclusion

Feature flags without targeting are on/off switches. Feature flags with targeting are precision instruments for:

  • Safe production testing with limited blast radius
  • Gradual rollouts that validate before scaling
  • Environment-specific behavior
  • Premium features for premium customers
  • Regional customization
  • Platform-specific optimizations

The difference between "should we launch this?" and "who should see this first?" is the difference between hoping for the best and engineering for success.

Start simple - target your team, then beta users, then segments. As you master targeting, you'll wonder how you ever deployed features any other way.

Your users aren't a monolith. Your feature rollouts shouldn't be either.