Variadic Functions in Go
Writing functions that accept a variable number of arguments, and the slice-spreading syntax that goes with them.
2 min read
A variadic function accepts any number of arguments of a given type — zero, one, or a hundred — without the caller needing to build a collection first. You've already used one: fmt.Println is variadic, which is why it accepts any number of arguments.
Declaring a variadic parameter
Prefix the final parameter's type with ...:
func sum(numbers ...int) int {
total := 0
for _, n := range numbers {
total += n
}
return total
}Calling it looks exactly like calling any other function, with as many or as few arguments as you like:
sum() // 0
sum(1) // 1
sum(1, 2, 3) // 6
sum(1, 2, 3, 4) // 10Inside the function, it's just a slice
Within the function body, numbers has type []int — an ordinary slice. There's no special variadic type to learn; the range loop above works exactly like it would on any slice, because that's what it is.
Only the last parameter can be variadic
func labelSum(label string, numbers ...int) string {
total := sum(numbers...)
return fmt.Sprintf("%s: %d", label, total)
}A function can mix regular parameters with a single variadic one, but the variadic parameter must always come last — Go needs to know unambiguously where the fixed arguments end and the variable-length ones begin.
Spreading a slice into a variadic call
If you already have a slice and want to pass its elements as individual variadic arguments, use ... at the call site too:
values := []int{1, 2, 3, 4, 5}
total := sum(values...) // spreads the slice, doesn't pass it as one []int argumentWithout the trailing ..., this wouldn't compile — sum expects individual ints (packaged as ...int), not a single []int. This spread syntax is exactly how you'd forward an already-collected slice into a variadic function, which comes up often when wrapping another variadic function.
A real-world shape: functional options
Variadic parameters are the backbone of a very common Go pattern for configurable constructors, since Go has no method overloading or default parameter values:
type ServerOption func(*Server)
func WithPort(port int) ServerOption {
return func(s *Server) { s.port = port }
}
func WithTimeout(seconds int) ServerOption {
return func(s *Server) { s.timeoutSeconds = seconds }
}
func NewServer(opts ...ServerOption) *Server {
s := &Server{port: 8080, timeoutSeconds: 30} // sensible defaults
for _, opt := range opts {
opt(s)
}
return s
}server := NewServer(WithPort(9000), WithTimeout(60))
defaultServer := NewServer() // uses all the defaultsThis "functional options" pattern lets callers specify only the settings they care about, in any combination, without Go needing overloaded constructors or optional parameters — both of which the language deliberately omits in favor of patterns like this one.
Test what you just learned
4 quick questions. Get all of them right to unlock the next lesson.
You can take the quiz without an account — logging in just lets your result count toward your progress.