Question: There're certain things in code that can't be conditional. Two very common examples:

* static types
* dependencies.

You can't naturally enable/disable a feature that implies a new dependency or a new type with a condition (a feature flag).

How do the proponents of trunk-based development tackle such cases?

1. `package.json`

Suppose a new feature requires a new dependency. Now 2 versions have to somehow coexist in `main` and be controlled by some ENV var. DIY machinery around `package.json` and logically nested versioning around this file sounds very dangerous. You basically reimplement low-level VCS-like functionality for a single file.
2. Types

This can be mitigated to some degree in some languages but not in the others.
For example in TypeScript, types can be dynamically inferred so it's possible to:

```
export const foo = FEATURE_ENABLED ? _newFoo : _oldFoo
export type Foo = typeof Foo

```

Still it's pretty messy and does not answer how to approach the same problem in other statically typed languages.

Answer: 
Feature flags should be used mainly for controlling the *visible behaviour* of a system from the outside. Introducing new types, or adding a dependency should not change that behaviour immediately, as long as the new type or code from the dependency are not used somewhere. And the usage itself can be controlled by a feature flag.

Modifications to existing types and dependencies can often be handled by keeping the old and new types in parallel in the code as long as the feature flag exists, or by keeping the old and new dependency both in the code, where this is possible.

Of course, when your software makes use of reflection to activate some new feature automatically as soon as a new type or dependency is available, then you need to work around this. But such mechanics is usually implemented dynamically and can hence be handled easily at run time.

**However** - in your comments, you asked: what about

1. Introducing new versions of existing dependencies, where you cannot easily install two versions in parallel, and the new dependency is not fully backwards compatible?
2. Introducing new versions of existing types, where maintaining these two versions in parallel causes some cascading effekt throughout a huge part the code base?

For those cases I think feature flags are indeed not the ideal tool. Separate branches give you the chance to develop and test some code to deal with some incompatiblities first in isolation, which gives you a lower risk of breaking anything before merging it back into the trunk. The disadvantage is that you may have more work to resolve merge conflicts. Hence it is sometimes good to have two different tools in your toolbox and know when to choose which.

Let me add I have a lot of first-hand experience with both approaches. Where I work, we handle at least 95% of our feature development with feature flags, and maybe 5% or less by feature branches. But YMMV.
Excellent points re the limitations of feature flags. However, I think there is limited scope for applying this criticism to trunk based development.

If you think about it, these kind of changes are a problem however you do em. A branch will cause merge conflicts, shared dependencies cant be rolled back etc

Trunk based development ignores the problems that branching is meant to solve. ie multiple features being worked on at the same time independently.

Trunk based says. instead of that, lets check in small changes so fast that we are all effectively working on the same branch. We don't merge one feature first and then another, we don't roll back, we don't have releases.

If you are always going forward then the kind of changes you are talking about don't pose a problem. the extra enum value is hidden, the extra dependency is there for everyone.