Sunday, August 16, 2026
Variadic Functions in Go: Common Mistakes and Gotchas
Posted by

The last parameter names ...string is a []string inside the function. Passing a, b, c builds a new slice. Passing s... hands over the same slice header. Mix those styles in one call and it does not compile. Forget ... on []any and fmt.Println prints one slice, not the elements.
The failure
This does not compile:
package main
func greet(who ...string) {}
func main() {
kids := []string{"James", "Jasmine"}
greet("Mom", kids...) // too many arguments
}
This compiles and mutates the caller's data:
func upper(names ...string) {
for i := range names {
names[i] = strings.ToUpper(names[i])
}
}
func main() {
list := []string{"alice", "bob"}
upper(list...)
fmt.Println(list) // [ALICE BOB]
}
And this logs the wrong shape:
kids := []any{"James", "Jasmine"}
fmt.Println(kids...) // James Jasmine
fmt.Println(kids) // [James Jasmine]
Calling process() with no arguments, where process takes values ...int, gives values == nil, not a non-nil empty slice.
Why this happens
The spec allows either a list of T or one []T followed by .... Not both. There is no place to splice extra values onto a spread.
s... does not copy the array. The function receives the same header. Element writes are visible to the caller. Individual arguments allocate a fresh array, so those writes stay local.
Zero trailing arguments leave the parameter nil. range over nil is fine. But process([]int{}...) passes a non-nil empty slice, so values == nil is false there. If your code cares about "no arguments", check len(values) == 0, which is true in both cases.
A ...any call without ... boxes the slice as one any. Loggers and slog hit this constantly.
The fix
Spread only a complete slice. To prepend values, append first:
greet(append([]string{"Mom", "Dad"}, kids...)...)
Copy if the callee must not mutate:
copied := append([]string(nil), list...)
upper(copied...)
Use s... when you mean elements. Pass s only when you want one argument. Treat nil vs empty explicitly if your API cares. Prefer a []T parameter when callers already own a slice and you do not want the two call styles.