Feature Flag Implementation Pitfall: When Flags Always Return True

๐Ÿ“– 3 minutes read

Feature flags are supposed to give you control. Enable a feature for 10% of users, test in production safely, roll back instantly if something breaks.

But what happens when your feature flag always returns the same value – no matter what’s in the database?

The Bug

I was debugging why a new feature was enabled for all records, even though we’d only flagged it for a handful. The model had a clean feature flag method:

class ProductType extends Model
{
    public function hasAdvancedPricing(): bool
    {
        // return $this->advanced_pricing_enabled;
        return true; // TODO: Enable flag check after testing
    }
}

See the problem?

The actual database check was commented out. The method just returned true for everything. The TODO comment suggests this was temporary during development – but it made it to production.

The Impact

Everywhere the app checked if ($productType->hasAdvancedPricing()), it got true. The entire feature flag system was bypassed.

  • Records that should use the old pricing logic were using the new one
  • The database field advanced_pricing_enabled was ignored
  • Gradual rollout wasn’t possible – it was all-or-nothing

The Fix

class ProductType extends Model
{
    public function hasAdvancedPricing(): bool
    {
        return (bool) $this->advanced_pricing_enabled;
    }
}

Now the flag actually checks the database. Revolutionary.

How This Happens

Development shortcut becomes permanent. Someone comments out the real check to force-enable a feature during testing. They forget to uncomment it before merging.

No test coverage on flags. If you’re not testing hasAdvancedPricing() with both true and false database values, you won’t catch this.

Silent failures. The app doesn’t crash. It just behaves incorrectly. Feature flags fail open instead of closed.

Better Pattern: Explicit Database Check

Make the database read explicit and obvious:

class ProductType extends Model
{
    protected $casts = [
        'advanced_pricing_enabled' => 'boolean',
    ];

    public function hasAdvancedPricing(): bool
    {
        // Explicit attribute access - harder to comment out by accident
        return $this->getAttribute('advanced_pricing_enabled');
    }
}

Or use an enum if your flag has multiple states:

enum PricingMode: string
{
    case LEGACY = 'legacy';
    case ADVANCED = 'advanced';
}

class ProductType extends Model
{
    protected $casts = [
        'pricing_mode' => PricingMode::class,
    ];

    public function hasAdvancedPricing(): bool
    {
        return $this->pricing_mode === PricingMode::ADVANCED;
    }
}

Now it’s impossible to return a hardcoded value without explicitly ignoring the enum.

Test Your Flags

Write tests that verify both states:

/** @test */
public function feature_flag_respects_database_value()
{
    $enabled = ProductType::factory()->create(['advanced_pricing_enabled' => true]);
    $disabled = ProductType::factory()->create(['advanced_pricing_enabled' => false]);

    $this->assertTrue($enabled->hasAdvancedPricing());
    $this->assertFalse($disabled->hasAdvancedPricing());
}

If this test fails, your flag is broken.

The Lesson

Feature flags are only useful if they actually read the database. Always check that your flag methods aren’t returning hardcoded values. And test both states – enabled and disabled – to catch this bug before it ships.

Otherwise, you’re not feature-flagging. You’re just writing complicated if (true) statements.

Daryle De Silva

VP of Technology

11+ years building and scaling web applications. Writing about what I learn in the trenches.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *