Drift: Feature Flags without Feature Flags
Flagging Without Feature Flags
Every feature flag you ship is a promise you'll clean it up later. You won't.
Six months from now that flag is still there serving true to everyone. One fateful day a butterfly flaps its wings and your application fails to load flags, the fallback value is still false. Surprise revert for everybody!
Flag cleanup is the tax nobody budgets for. You pay it in cognitive load, in dead branches that look alive, in deploy anxiety when someone asks "wait, is that flag still doing anything?" The tooling around feature flags is sophisticated — percentage rollouts, user targeting, kill switches — but the lifecycle always ends the same way. A human has to go in, understand what the flag was doing, verify that one path is dead, rip out the conditional, delete the variations, and hope they didn't miss a spot. It's tedious, error-prone, and it scales with every flag you add.
After seeing this pattern play out countless times over the 6 years I've worked for LaunchDarkly, a crazy idea kept sneaking back into my head: what if the flag never touched your code in the first place?
Not "what if we had better linting rules" or "what if we added a Slack reminder." What if the mechanism was fundamentally different — what if you could run two versions of a module side by side, dispatch between them per-request, and when you're done, just... stop. Delete the old commit reference. Your code never had a conditional. There's nothing to clean up because there was never anything to remove.
This isn't a completely new idea. Erlang and the BEAM VM have done hot code reloading for decades — running old and new versions of a module simultaneously, draining connections from one to the other, with no downtime and no flags. Dark Lang took it further, building a whole platform around versioned handlers where every function is an immutable snapshot and traffic shifts between them. GitHub's Scientist library approached it from a different angle — shadow-testing new code paths against old ones in production, comparing results without affecting users.
This project takes a bit from each. From Erlang, the idea that "two versions running at once" is a runtime concern, not a code concern. From Dark, the conviction that versions should be addressable artifacts, not branches in an if-statement. From Scientist, the principle that you should be able to observe both paths before committing. (We haven't built the shadow testing piece yet. But the architecture supports it.)
The result is drift — a Node.js tool that enables module-level progressive delivery backed by LaunchDarkly. You point it at a module. You commit a new version. You tell LaunchDarkly who should get what. The runtime handles everything else. Your source code never sees an if statement. When the rollout is done, you remove the drift configuration. There is no flag to clean up because there was never a flag in your code.
Drift comes with a simple command line tool for managing releases. In the future, I'd like to deeply integrate it with both CI/CD pipelines and IDEs via an extension.
Initial Setup
We have a simple express.js application with a route:
In app.js:
;
;
'/discount', getDiscount;
In routes/pricing.mjs:
We are going to be iterating on the pricing module. The first step is to set the base commit with drift init

Drift created a feature flag for us and detected all of the exported functions. Now let's make some changes! We're going to add a new coupon code called SAVE30 and commit.
- const discounts = { SAVE10: 0.1, SAVE20: 0.2 };
+ const discounts = { SAVE10: 0.1, SAVE20: 0.2, SAVE30: 0.3 };
We can then mark this commit as one we want to be able to control at runtime:
)
A new variation is created but it's not yet being served:

Runtime
When you run the application normally, you're just running your local copy of the code. No feature flags are evaluated, in fact: the LaunchDarkly SDK isn't even started up at all. In order to opt-in to runtime control, we can import drift/register:
Let's try hitting the discount endpoint:
}
So why did we see an error instead of our 30% discount? Drift routed our request to the initial version of this code because we haven't updated the targeting rules yet.
Let's enable it for just our user:

When we make our request again, we see the discount is available for only us!
}
# try again with a different user
}
Promotion
The Drift CLI includes a helper to promote a marked version to be the default served to everyone. When you promote a version, the previous default will be set as the off variation. This means we can easily rollback to the previous version by simply toggling the flag off. Drift will also update the fallback value to match the off variation for you. This state is stored as a part of drift's manifest which should be bundled with your deployed application.
)
If you want to set the fallback value manually, you can do that with drift fallback <releaseid> <fallback>. If you've had a flag out for a while, you may want to do this to prevent accidental rollbacks:
)
You can always see the current state using drift status
)
When you're using a percentage rollout in the default rule, the weights will be shown:
)
Monitoring

All exported functions in drifted modules are automatically instrumented with error and duration metrics. So out of the box, every single release can be a guarded release.

We can take this even further in the future by supporting shadow evaluations for side-effect free functions. For example, imagine you are making changes to your access control system. A future version of drift could evaluate both the current and next versions of your function and compare the results, reporting divergences as error events. While the experiment is running, only values from the current version will be returned to users. This is very similar to a 4-stage migration in LaunchDarkly, just without the writing.
Cleanup
Cleanup is a piece of cake: just run drift archive
If you archived a flag from the UI instead of using the drift cli, you can run drift remove pricing or use the drift cleanup command to check all of the releases in your manifest at once:
)
)
No code changes required, your codebase is always moving ahead. Drift gives your modules the ability to time travel to different versions of your code.
Conclusion
This experiment went better than I could have ever expected. While this system is framework and runtime specific, we can build on the patterns used for automatic instrumentation in APM libraries to speed up implementation. For releases, I think the future is flagless.
I plan to open-source drift in the coming weeks as an experimental project. Bringing it up to a production-grade system will be left as an exercise for somebody else to take on (maybe you?).