Variadic Functions
Learn how to write Go functions that accept a variable number of arguments using the ...type syntax, and how to spread a slice into one.
Introduction
Sometimes you don't know in advance how many arguments a function call will need — think of fmt.Println, which happily accepts one argument, five, or none at all. Go supports this through variadic functions: functions that accept zero or more arguments of a specified type using the ...type syntax.
- How to declare a variadic parameter with ...type.
- How variadic arguments are collected into a slice inside the function.
- How to spread an existing slice into a variadic call with ...
- How to combine variadic parameters with regular ones.
Declaring a Variadic Function
A variadic parameter is written as name ...Type and must be the last parameter in the function's signature. The caller can then pass any number of arguments of that type — including none at all.
package main
import "fmt"
func sum(numbers ...int) int { total := 0 for _, n := range numbers { total += n } return total}
func main() { fmt.Println(sum()) // 0 arguments fmt.Println(sum(5)) // 1 argument fmt.Println(sum(1, 2, 3, 4)) // 4 arguments}Click Run to see what this code prints.
Variadic Parameters Are Slices
Inside the function body, a variadic parameter behaves exactly like a regular slice of that type. This means you can use len, range, indexing, and every other slice operation on it directly.
package main
import "fmt"
func describe(items ...string) { fmt.Printf("Type: %T, Length: %d\n", items, len(items)) for i, item := range items { fmt.Printf(" [%d] %s\n", i, item) }}
func main() { describe("go", "python", "rust")}Click Run to see what this code prints.
Spreading a Slice Into a Call
If you already have a slice and want to pass its elements as individual variadic arguments, use the ... spread operator after the slice at the call site. Without it, Go would try to pass the whole slice as a single argument, which doesn't match a variadic parameter's expected type.
package main
import "fmt"
func sum(numbers ...int) int { total := 0 for _, n := range numbers { total += n } return total}
func main() { values := []int{10, 20, 30, 40}
// Spread the slice's elements as individual arguments. fmt.Println(sum(values...))}Click Run to see what this code prints.
Mixing Regular and Variadic Parameters
A function can have regular parameters before its variadic one, but the variadic parameter must always come last — Go does not allow anything after it.
package main
import "fmt"
func logMessage(level string, messages ...string) { fmt.Printf("[%s] ", level) for _, m := range messages { fmt.Print(m, " ") } fmt.Println()}
func main() { logMessage("INFO", "Server started on port", "8080") logMessage("ERROR")}Click Run to see what this code prints.
A Real Example: Building sum() and Println-style Logging
fmt.Println itself is variadic — its signature is func Println(a ...any) (n int, err error) — which is exactly why it accepts any number of arguments of any type. Here's a small logger that mimics that flexibility.
package main
import "fmt"
func maxOf(numbers ...int) (int, error) { if len(numbers) == 0 { return 0, fmt.Errorf("maxOf: at least one number is required") } max := numbers[0] for _, n := range numbers[1:] { if n > max { max = n } } return max, nil}
func main() { if m, err := maxOf(3, 7, 2, 9, 4); err == nil { fmt.Println("max:", m) }
if _, err := maxOf(); err != nil { fmt.Println("error:", err) }}Click Run to see what this code prints.
Common Mistakes
- Trying to place a variadic parameter anywhere except last in the parameter list — this fails to compile.
- Forgetting the ... spread operator when passing an existing slice into a variadic call, causing a type mismatch error.
- Declaring more than one variadic parameter in a single function — only one is allowed.
- Assuming a variadic parameter with zero arguments is nil in a way that breaks len() or range — it's a nil slice, and both work fine on it.
- Overusing variadic parameters for functions that really need a fixed, well-documented set of arguments.
Best Practices
- Use variadic parameters when the number of arguments is genuinely open-ended, like sum, logging, or formatting functions.
- Validate len(args) inside the function if zero arguments is not a valid case for your logic.
- Prefer a slice parameter over a variadic one if callers will almost always already have a slice — spreading everywhere adds noise.
- Document what a variadic parameter represents, especially when mixed with regular parameters, since the signature alone can be ambiguous.
- Avoid excessive variadic parameters of type any (interface{}) unless you genuinely need heterogeneous arguments — they sacrifice type safety.
Frequently Asked Questions
Yes — calling a variadic function with zero arguments gives it an empty (nil-backed) slice internally, and len() on it correctly returns 0.
Its variadic parameter type is ...any (interface{}), which can hold values of any type, letting it accept a heterogeneous list of arguments.
No, Go only allows a single variadic parameter, and it must be the final one in the parameter list.
Key Takeaways
- A variadic parameter is declared with name ...Type and must be the last parameter.
- Inside the function, a variadic parameter behaves like a normal slice.
- Use the ... spread operator to pass an existing slice's elements into a variadic call.
- Regular parameters can precede a variadic one, but nothing can follow it.
- fmt.Println and similar standard library functions rely on variadic parameters for their flexibility.
Summary
Variadic functions give Go the same flexible-argument convenience you may have used in other languages, while keeping the type system explicit and predictable. Next, you'll learn about closures — functions that capture and remember variables from the scope they were created in.