PATTERN Cited by 1 source
Build-tag dependency isolation¶
Intent¶
Keep an unwanted dependency out of some Go binaries by marking the file that imports it with a build tag the wanted binaries don't pass. The compiler skips the file, its imports never reach the linker, and the transitive dep tree rooted at it is gone.
When to use¶
- A library optionally supports a feature whose implementation drags in heavy dependencies (e.g. a runtime-plugin facility, vendor-SDK integrations, rarely-used codec).
- Most consumers don't use the feature and would rather not pay its binary-size cost.
- Splitting the library into sub-packages would break API compatibility or is upstream-unfriendly.
Mechanism¶
- In the importing file, add a build tag:
- Consumers that need the feature pass
-tags optional_feature; everyone else builds without it. - The compiler's file-selection pass excludes the file entirely,
so
heavy/dep— and its full transitive closure — never reach the linker.
Canonical wiki instance: containerd's plugin import¶
containerd/plugin/plugin_go18.go unconditionally imported the
stdlib plugin package for user-loadable containerd plugins.
Any program transitively importing containerd paid the
245-MiB-and-rising cost of
plugin-mode
linking.
Datadog's upstream fix — containerd#11203
— added a build tag gating the plugin import. Downstream
consumers (like the Datadog Agent) build
without the tag → plugin import doesn't exist in their binary
→ method-DCE re-engages → 245 MiB recovered, ~75 % of Agent users
benefit. Instance of patterns/upstream-the-fix.
Relation to package-split¶
The complementary pattern is patterns/single-function-forced-package-split:
| Pattern | When |
|---|---|
| Build-tag isolation | Dep-importing code is optional behaviour within a package most consumers want to import unchanged. |
| Package-split | The dep-importing code is one function / symbol in a package consumers unavoidably import for other reasons. |
Both prune transitive imports at their source. Both are standard Go techniques. Which one fits depends on whether the optionality is natural to expose at the file/tag boundary (build tags) or at the package boundary (split).
Forces¶
- + Low code churn, upstream-friendly (single-file diff with a tag directive).
- + Composable with the feature-per-binary build matrix that large Go programs already use.
- + No API break.
- − Tag names become API. Adding a tag to remove an import is a behaviour change any downstream depending on the current default must opt into or out of.
- − Users who want the feature have to know to pass the tag.
- − Not a substitute for package discipline. If a single file keeps accumulating optional imports, split the package.
Seen in¶
- sources/2026-02-18-datadog-how-we-reduced-agent-go-binaries-up-to-77-percent
— canonical containerd
plugininstance; Datadog's Agent build matrix built on this pattern throughout.