How to Ship Faster, Safer, and With Less Drama
Feature Toggling & Trunk-Based Development for Continuous Delivery
Shipping software shouldn’t be a gamble. But for many teams, every release feels like one.
Long-lived branches pile up, and merge conflicts become a nightmare. Deployments are high-stakes events, and when something goes wrong, rolling back is a mess.
This isn’t just an engineering problem. It’s a business problem. Slow releases mean slow feedback, which means wasted effort and missed opportunities.
Shipping software should be like breathing—constant, natural, low-drama.
With continuous delivery being de facto mainstream, this goal is theoretically within the reach of any team or organization. Smaller changes mean fewer surprises. Frequent releases mean faster feedback.
However, continuous delivery only works if your team can integrate changes safely. That’s where Trunk-Based Development and Feature Toggling come in.
Trunk-based development keeps teams moving. Feature toggling keeps releases safe. Together, they make continuous delivery a reality. This article breaks down how to make both work in practice.
Trunk-Based Development
Many teams don’t struggle with writing code, but getting it into production without breaking everything.
For years, one approach for this has been long-lived feature branches, aka. Feature-Based Development (FBD):
Note: In this article I call the default branch in the git repository “main”.
A developer starts a feature, branches off main, and works in isolation. Days or weeks later, they try to merge it back. But by then, main has changed, and the branch is out of sync.
Merge conflicts pile up. The code review is a mess. The release gets delayed.
A situation known as merge hell—integrating code takes longer than writing it.
Trunk-Based Development (TBD) is the very opposite of FBD. Developers merge small, frequent changes directly into main via short-lived branches that last hours or days, not weeks.
The result?
Fewer merge conflicts → Changes are small and integrate continuously.
Faster feedback loops → Code gets reviewed and deployed quickly.
Higher stability → main is always ready for release.
⚠️ There is a caveat: When you frequently merge to main, you can’t afford to introduce half-baked features that break production.
Feature flags help you integrate work early and hide unfinished functionality behind a toggle. This keeps main stable while allowing continuous integration and early testing in real environments.
Feature Toggling
Feature toggles (or feature flags) let you separate deployments from releases.
Code can be merged and deployed continuously, but features are only enabled when you’re ready. Some features need more testing. Others should only be available to a subset of users. Or if you are rolling out experimental change, you want the ability to disable it instantly (a kill-switch) if things go sideways.
At its core, a feature flag is a simple conditional statement:
Instead of using hardcoded values, feature flag services like LaunchDarkly or Unleash let you dynamically control which features are enabled, for whom, and when.
Feature flags are not just for enabling new features to users, but they can be categorized into different types:
🚀 Release Flags – Hide unfinished features until they’re ready.
⚙️ Operational Flags – Enable or disable features instantly in case of issues.
🧪 Experiment Flags – A/B test variations and measure impact.
🔒 Permission Flags – Control access based on user roles.
Example: Using LaunchDarkly in a React App
To see feature toggling in action, let’s integrate LaunchDarkly into a React application using the launchdarkly-react-client-sdk.
Let’s start with installing the package into your existing React application (or create one using this doc):
npm install launchdarkly-react-client-sdk
The next step is to initialize the client SDK with your project’s ID and any additional information, such as the currently logged-in user. To get the ID, you can register for the free Developer plan, which has a limit of 1,000 monthly active users (more than enough for testing it out).
With the initialization out of the way, accessing and using a feature flag (in this case called newFeatureEnabled is as easy as:
The last piece of the puzzle is creating the flag in the LaunchDarkly UI (or via Terraform).
When the flag is created, it is by default in the disabled (OFF) state. In the example below, you can see our flag enabled and scheduled for Progressive rollout, which in given intervals enables the new feature for more and more users.
You can use many strategies, such as percentage rollouts, region-based rollouts, customer-based rollouts, etc. The main benefit is that you can change who will see new features and when without deploying new code.
Best Practices for Feature Flags
Keep flags short-lived: Don’t let old flags accumulate as tech debt. Remove them once a feature is fully rolled out.
Use a feature flag management tool: Manually toggling flags via config files is a bad idea. Services like LaunchDarkly provide better control.
Combine feature flags with analytics: Track how feature rollouts affect user behavior, key metrics, and system performance.
Test features separately: Ensure that both the enabled and disabled states work correctly.
Plan for quick rollbacks: Make sure toggles can be switched off instantly if something goes wrong.
Common Anti-Patterns
Feature Flag Tech Debt
Feature flags are meant to be temporary, but teams often forget to remove old ones. Over time, the codebase becomes cluttered with dead toggles, making it harder to maintain and developers waste time navigating code paths that shouldn’t even exist anymore.
✅ Track your feature flags - Use a dedicated feature flag management tool (such as LaunchDarkly) to keep a record of all active flags.
✅ Set an expiration date - Every toggle should have a planned removal date. If a flag has been active for weeks with no change, it should raise a notification and the flag should be reviewed (and ideally removed).
Relying on Feature Flags Instead of Testing
Some teams use feature flags as an excuse to skip proper testing.
The logic is: “If something goes wrong, we’ll just turn it off.”
But this doesn’t prevent regressions. It just hides bad code until accumulated tech debt cannot be ignored anymore.
✅ Test both ON and OFF - Ensure that both flag states function correctly.
✅ Write automated tests - Don’t assume toggled-off code is harmless.
Poor Observability
A new feature is rolled out, but the team has no way to track how is it used, who is using it, or how reliable or performant it is.
✅ Use feature flag analytics – Track who has the flag enabled and monitor reliability and performance metrics for the new feature (in tools such as DataDog).
✅ Log feature flag state – Include flag values in your logs, or create separate log entries per flag states, so they are easily filterable in your observability tool.
Overcomplicating
Some teams create deeply nested toggles, making it impossible to understand what code is actually running. Too many interdependent flags make debugging a nightmare.
if (featureAEnabled) {
if (featureBEnabled) {
if (!featureCEnabled) {
doSomething();
} else {
doSomethingElse();
}
}
}
✅ Keep flags simple – Each flag should control a single feature, not multiple behaviors.
✅ Avoid deeply nested logic – Don’t mix multiple feature flags together.
if (featureAEnabled) {
doFeatureA();
}
if (featureBEnabled) {
doFeatureB();
}
Summary
I hope this article gave you an actionable overview of benefits of Trunk-based development paired with Feature flags. In short:
Trunk-Based Development eliminates the pain of long-lived feature branches, keeping teams in sync and reducing merge conflicts.
Feature Toggling ensures that new features can be integrated early and safely, allowing controlled rollouts and instant rollbacks without risky deployments.
I'd love to hear about your best practices for shipping code with confidence!
📖 Read Next
Discover more from the Product Engineering track:
If you’re looking for a space where you can learn more about software engineering, leadership, and the creator economy, with Dariusz Sadowski, Michał Poczwardowski, and Yordan Ivanov 📈, we’ve created the Engineering & Leadership discord community:
📣 Recommended Reading
5 lessons learned setting up a global engineering org at Google by
and20 Business terms every Engineering Manager should know by
inI Became a Monk for 10 Days so You Don't Have To by
in
Love the practical advice on the trunk-based development and feature flags.
There's one aspect of the release speed/cadence that you did not mention explicit, and I did a few weeks ago, that is the idea that "control decays exponentially with each day that passes after a release".
I make the argument that we should have metric for software deliveries: days since last release, where with each day that passes, we have MORE risk of not being able to release.
Here's that post: https://open.substack.com/pub/vascoduarte/p/how-to-control-a-software-project?r=3a1rs5&utm_campaign=post&utm_medium=web&showWelcomeOnShare=true
I'd love to read your thoughts on that short article :)
Big fan of trunk-based development.
One technique that I would suggest to put in practice to achieve this is using TDD.