Handling Technical Disagreements on Go Teams
Facilitation patterns for idioms vs frameworks debates.
Go teams argue about the right kind of things: stdlib versus routers, generics versus interfaces, monolith versus modules, errgroup versus channels.
Healthy disagreement improves designs.
Unfacilitated disagreement becomes incompatible services and bitter review threads.
Summary
Technical disagreements on Go teams usually involve trade-offs between simplicity, velocity, and operational familiarity.
Facilitation means making options explicit, scoring against agreed criteria, assigning a decision owner, and recording outcomes in ADRs or team standards.
The goal is not unanimous enthusiasm.
It is disagree and commit with a review date when data may change the call.
Recipe
Quick-reference recipe card - copy-paste ready.
## Technical decision: <topic>
Options:
1. ...
2. ...
Criteria (weighted):
- operability
- team familiarity
- compile/deploy time
- consistency with ADR index
Decision owner: @name
Deadline: <date>
Fallback review: <quarter>When to reach for this:
- Split review on merging chi vs stdlib mux
- Platform mandates stdlib-only; squad wants gin bindings
- Senior engineers deadlock on package layout
- Staff proposal conflicts with squad autonomy
- Repeated reverts on the same architectural choice
Working Example
Two seniors disagree on worker concurrency: channels vs errgroup.
## Decision record (meeting output)
Topic: ingest worker orchestration
Options:
A) errgroup + SetLimit
B) worker pool channel + fixed goroutines
Criteria scores (1-5):
| Criterion | A | B |
|-----------|---|---|
| Shutdown clarity | 5 | 3 |
| Team familiarity | 4 | 4 |
| Testability | 5 | 4 |
Decision: A for new ingest worker; B remains in legacy billing until ADR 0031 superseded
Owner: @tl
Review: after Q3 load test// Agreed reference snippet for option A
g, ctx := errgroup.WithContext(ctx)
g.SetLimit(8)
for _, job := range batch {
g.Go(func() error { return process(ctx, job) })
}
return g.Wait()What this demonstrates:
- Options named before debate reopens implementation details
- Criteria table makes subjective preferences discussable
- Legacy exception documented instead of silent dual patterns
- Reference snippet aligns future PRs
Deep Dive
How It Works
- Tech lead schedules 45-minute decision session with doc circulated in advance.
- Each advocate writes one page: option description, risks, migration cost.
- Group scores criteria; decision owner picks unless escalation threshold hit.
- Outcome logged in ticket + ADR when cross-service; reviewers enforce in PRs.
Common Go Debate Patterns
| Debate | Underlying tension | Facilitation tip |
|---|---|---|
| stdlib vs chi/gin | Consistency vs DX | Org default + documented exceptions |
| generics vs interfaces | Clarity vs abstraction | Prototype both on same API sketch |
| monolith vs micro-module | Compile speed vs autonomy | Measure tag churn before split |
| sync vs async worker | Latency vs complexity | Load test before choosing channels |
| slog vs zap | Stdlib vs ecosystem | Pick per observability ADR |
Go Notes
// When debating error libraries, anchor on stdlib first
if errors.Is(err, ErrNotFound) {
return mapNotFound()
}- Prefer decisions that reduce import fan-out and magic init.
- Frame framework adoption as dependency + training cost, not taste.
- Use linters to encode agreed defaults (
depguardbanned imports).
Gotchas
- Debate in PR comments - Fix: Pause PR; run decision template; resume with ADR link.
- Consensus required - Fix: Name decision owner; escalate to staff/architect if needed.
- No review date - Fix: Schedule quarterly revisit when metrics may change minds.
- Winner takes all - Fix: Allow phased migration for legacy paths with sunset ADR.
- Arguments from authority only - Fix: Require criteria scores and data (latency, LOC, incidents).
- Silent grumbling after decision - Fix: Document dissent in ADR "Notes" section; commit anyway.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
| Spike both options | Evidence thin | Decision blocks multiple teams weeks |
| Escalate to architect council | Cross-org impact | Squad-local handler style |
| Default to stdlib minimalism | No data, equal scores | Framework clearly saves weeks |
| Defer decision | Low impact fork | Production concurrency choice |
FAQs
Who should own the final call?
Tech lead for squad scope; staff/platform for cross-cutting defaults; engineering director for policy exceptions.
How long should debates run?
One focused session plus async doc comments - usually under one week for squad decisions.
What if two options score equally?
Pick org default (often stdlib simplicity) and set review date after next benchmark or launch.
Should dissent be recorded?
Yes in ADR notes - helps future reviewers understand context without reopening fight.
How handle public disagreement in Slack?
Move to decision doc thread; keep Slack for facts, not polls that bypass criteria.
When is escalation appropriate?
When decision affects SLOs, security posture, or multiple teams' module graphs.
Can teams override platform ADRs?
Only with superseding ADR approved by platform owner - not local README exceptions.
How prevent idioms vs frameworks religion?
Measure outcomes: incident count, review time, onboarding weeks - not slogans.
What about strong junior dissent?
Welcome criteria input; decision owner still decides with leveling-appropriate scope.
Should we vote?
Use criteria scoring over raw votes - popularity misses operability constraints.
How revisit a settled decision?
Bring new data to review date or file superseding ADR with migration plan.
Does disagree-and-commit apply to safety issues?
No - block on security, data loss, or race risks regardless of process outcome.
Related
- Technical Leadership in Go Teams - leadership responsibility for facilitation
- Tech Leadership Basics - time-boxed disagreement template
- Writing ADRs for Go Architecture Decisions - record outcomes
- Design Reviews for Go Services - prevent late disagreements
- Code Review Culture for Go - keep debates out of nit threads
Stack versions: This page was written for Go 1.26.x (Green Tea GC default, go fix modernizers - verify patch at build), chi (latest - verify at build), gin (latest - verify at build), echo (latest - verify at build), google.golang.org/grpc (latest - verify at build), sigs.k8s.io/controller-runtime (latest - verify at build), kubebuilder (latest - verify at build), tinygo (latest - verify board targets at build), wazero (latest - verify at build), and golangci-lint (latest - verify linter set at build).