Go Variadic Functions — Find the Bug¶
Instructions¶
Each exercise contains buggy Go code involving variadic functions. Identify the bug, explain why it occurs, and provide the corrected code. Difficulty: 🟢 Easy, 🟡 Medium, 🔴 Hard.
Bug 1 🟢 — Variadic Not Last¶
package main
import "fmt"
func event(tags ...string, name string) {
fmt.Println(name, tags)
}
func main() {
event("auth", "user", "login")
}
What is the bug?
Hint
Where is the variadic parameter allowed in the parameter list?Solution
**Bug**: The variadic parameter `...string` must be the **last** parameter. **Compile error**: `can only use ... with final parameter in list`. **Fix** — put the required parameter first: **Key lesson**: Only the last parameter may be variadic. Required parameters go first.Bug 2 🟢 — Forwarding Without Spread¶
package main
import "fmt"
func inner(args ...any) {
fmt.Println("inner got", len(args), "args:", args)
}
func outer(args ...any) {
inner(args)
}
func main() {
outer("a", "b", "c")
}
The author expected "inner got 3 args: [a b c]". What does it actually print?
Hint
What is the difference between `inner(args)` and `inner(args...)`?Solution
**Bug**: `inner(args)` passes the slice `args` as a SINGLE argument (an `any` whose value is `[]any{a, b, c}`). `inner` receives 1 arg, not 3. Output: **Fix** — use spread: Now output: **Key lesson**: When forwarding a variadic, you MUST spread with `args...`. Forgetting the `...` wraps the slice as a single arg. This is the most common variadic bug.Bug 3 🟢 — Spread of []int Into ...any¶
What is the bug?
Hint
`fmt.Println` is `func(args ...any)`. Can you spread `[]int` into `...any`?Solution
**Bug**: `fmt.Println` takes `...any` (== `...interface{}`). Spreading requires the slice element type to match exactly. `[]int` is NOT assignable to `[]any` — Go does not perform per-element conversion. **Compile error**: `cannot use nums (variable of type []int) as []any value in argument to fmt.Println`. **Fix** — convert each element manually: Or just call without spread: **Key lesson**: Spread requires exact slice type match. To spread typed slices into `...any`, convert each element first.Bug 4 🟢 — Mixing Literal Args With Spread¶
package main
import "fmt"
func sum(xs ...int) int {
total := 0
for _, x := range xs {
total += x
}
return total
}
func main() {
extras := []int{2, 3, 4}
fmt.Println(sum(1, extras...))
}
What is the bug?
Hint
Can a single call mix individual arguments with spread?Solution
**Bug**: Go does not allow mixing literal arguments with the spread operator at the same call site. **Compile error**: `cannot mix...` (or similar — phrasing varies by Go version). **Fix** — build a combined slice first: Or just expand the call to all literals: **Key lesson**: Each call uses ONE form: all literal, or one spread. Combine slices yourself before the call.Bug 5 🟡 — Aliasing Mutation Bug¶
package main
import "fmt"
func zero(xs ...int) {
for i := range xs {
xs[i] = 0
}
}
func main() {
s := []int{1, 2, 3}
zero(s...)
fmt.Println(s)
}
The author expected [1 2 3]. What actually prints?
Hint
Does spread copy the slice or share the backing array?Solution
**Bug**: Spread (`s...`) shares the same backing array. `zero` mutates the caller's slice through aliasing. Output: **Fix** (option A — defensive copy inside `zero`): **Fix** (option B — caller copies): **Fix** (option C — return a result instead of mutating): **Key lesson**: Spread aliases the caller's backing array. If your function mutates, document it or defensively copy.Bug 6 🟡 — Variadic Call With nil Slice¶
package main
import "fmt"
func sum(xs ...int) int {
if xs == nil {
return -1 // sentinel for "no input"
}
total := 0
for _, x := range xs {
total += x
}
return total
}
func main() {
fmt.Println(sum()) // expects -1
fmt.Println(sum([]int{}...)) // expects 0?
fmt.Println(sum([]int{1, 2, 3}...)) // expects 6
}
The author expected -1, 0, 6. What actually prints?
Hint
What is the parameter when called with `[]int{}...` vs `sum()`?Solution
**Bug**: There's a subtle distinction: - `sum()` → `xs` is **nil** → returns -1. - `sum([]int{}...)` → `xs` is a non-nil empty slice → does NOT match `xs == nil`, returns 0. - `sum([]int{1, 2, 3}...)` → returns 6. Output: This may match expectations, but the bug is in the **design**: `xs == nil` is not a reliable "no args" indicator because callers can spread an explicitly-empty slice. **Fix** — use `len(xs) == 0` for the "no input" case: Now both `sum()` and `sum([]int{}...)` correctly return -1. **Key lesson**: Always check `len(xs) == 0`, not `xs == nil`. Both nil and empty-non-nil slices have length 0; users may pass either.Bug 7 🟡 — Storing the Variadic Without Copying¶
package main
import "fmt"
type Buffer struct {
items []int
}
func (b *Buffer) Append(items ...int) {
b.items = items // BUG
}
func main() {
src := []int{1, 2, 3}
b := &Buffer{}
b.Append(src...)
src[0] = 99
fmt.Println(b.items)
}
The author expected [1 2 3] to be stored. What actually prints?
Hint
What does `b.items = items` actually assign?Solution
**Bug**: `b.items = items` stores a slice that aliases the caller's backing array. After the caller mutates `src[0]`, the buffer reflects it. Output: **Fix** — defensive copy: Or, if you want to APPEND (not replace) like the method name suggests: `append` here automatically grows `b.items` and copies the elements, so no aliasing of the input slice remains. **Key lesson**: When a variadic function stores the slice past the call, defensively copy. The aliasing window is invisible from the function signature.Bug 8 🟡 — printAll Calling Itself With Spread¶
package main
import "fmt"
func printAll(args ...any) {
if len(args) == 0 {
fmt.Println("(empty)")
return
}
fmt.Println(args[0])
printAll(args[1:]) // BUG
}
func main() {
printAll("a", "b", "c")
}
The author expected each element printed on its own line. What actually happens?
Hint
What does `printAll(args[1:])` pass — a single arg or multiple?Solution
**Bug**: `args[1:]` is a slice. Passing it as a single argument means `printAll`'s next call sees `args == [[b c]]` (one arg, which is the slice). The recursive call enters with `len(args) == 1`, prints the slice, then recurses with `args[1:]` (empty), and prints `(empty)`. Output: **Fix** — spread with `...`: Now: **Key lesson**: Spread is required when forwarding a slice into a variadic — even when recursing into the same function.Bug 9 🟡 — Reslicing Inside Variadic¶
package main
import "fmt"
func keepFirst(n int, xs ...int) []int {
return xs[:n]
}
func main() {
s := []int{1, 2, 3, 4, 5}
kept := keepFirst(2, s...)
s[0] = 99
fmt.Println(kept)
}
The author expected [1 2]. What actually prints?
Hint
What does `xs[:n]` share with `s`?Solution
**Bug**: `xs[:n]` is a view of the same backing array as `xs`, which is the same as `s`. After `s[0] = 99`, `kept[0]` reflects the change. Output: **Fix** — copy the result: **Key lesson**: A subslice of a spread variadic is still aliased to the caller's backing array. Always copy when returning a slice that should be independent.Bug 10 🔴 — Variadic of error and Nil Filtering¶
package main
import (
"errors"
"fmt"
)
func combine(errs ...error) error {
if len(errs) == 0 {
return nil
}
msgs := ""
for i, e := range errs {
if i > 0 {
msgs += "; "
}
msgs += e.Error() // BUG
}
return errors.New(msgs)
}
func main() {
err := combine(nil, errors.New("a"), nil, errors.New("b"))
fmt.Println(err)
}
What is the bug?
Hint
What happens when you call `e.Error()` on a nil error?Solution
**Bug**: When `e` is nil (a nil `error` interface), `e.Error()` panics with `nil pointer dereference`. The author forgot to skip nil errors. **Fix** — filter nil errors first: Or use `errors.Join` (Go 1.20+): **Key lesson**: Variadics of interface types may contain nil values. Always check before dereferencing or method-calling.Bug 11 🔴 — Variadic Slice Reused in Goroutine¶
package main
import (
"fmt"
"sync"
"time"
)
type Producer struct {
buf []int
mu sync.Mutex
}
func (p *Producer) push(items ...int) {
p.mu.Lock()
defer p.mu.Unlock()
p.buf = append(p.buf, items...)
}
func main() {
p := &Producer{}
src := []int{1, 2, 3}
go p.push(src...)
src[0] = 99 // mutate while goroutine may be reading
time.Sleep(50 * time.Millisecond)
fmt.Println(p.buf)
}
This produces a race condition. Where, and how do you fix?
Hint
Spread aliases the caller's backing array. The goroutine reads `items`; the caller mutates `src` concurrently.Solution
**Bug**: `go p.push(src...)` passes the spread slice (aliasing `src`) to a goroutine. The main goroutine then mutates `src[0]`. Both goroutines access `src[0]` concurrently — **data race**. `go test -race` would detect: "WARNING: DATA RACE". The output may be `[1 2 3]` or `[99 2 3]` non-deterministically. **Fix** (option A — defensive copy at the boundary): **Fix** (option B — copy in `push`): **Fix** (option C — caller copies before goroutine): **Key lesson**: When a variadic spread crosses a goroutine boundary, the alias becomes a race risk. Defensively copy at the boundary or document the contract clearly.Bug 12 🔴 — Generic Variadic Type Inference Failure¶
package main
import "fmt"
func First[T any](xs ...T) T {
var zero T
if len(xs) == 0 {
return zero
}
return xs[0]
}
func main() {
fmt.Println(First()) // BUG
fmt.Println(First(1, 2, 3))
fmt.Println(First("a", "b"))
}
What is the bug at the first call?
Hint
Can the compiler infer `T` from no arguments?Solution
**Bug**: With zero arguments, the compiler cannot infer `T`. **Compile error**: `cannot infer T`. **Fix** — provide the type parameter explicitly: For other calls, type inference works because the first argument's type determines `T`. **Key lesson**: Generic variadics fail type inference at empty-args calls. Either require explicit type args or design the function to take at least one positional arg.Bonus Bug 🔴 — Empty Spread Doesn't Match What You Expect¶
package main
import "fmt"
func sum(xs ...int) int {
fmt.Printf("xs is nil? %v, len=%d\n", xs == nil, len(xs))
total := 0
for _, x := range xs {
total += x
}
return total
}
func main() {
var nilSlice []int
empty := []int{}
fmt.Println(sum()) // prints "xs is nil? true, len=0" → 0
fmt.Println(sum(nilSlice...)) // prints ?
fmt.Println(sum(empty...)) // prints ?
}
What does each call print for the nil/len status?