Question
In Python and Ruby, the splat operator (*) can unpack an array into function arguments. JavaScript can use .apply() or spread syntax. Can Go unpack an array or slice as arguments to a function?
For example, how can a slice be used with this function?
func myFunc(a, b int) int {
return a + b
}
func main() {
arr := []int{2, 4}
sum := myFunc(arr) // Is there a Go equivalent of argument unpacking?
_ = sum
}
Short Answer
Go does not support unpacking a slice into a function with a fixed number of parameters such as func(int, int). However, Go supports slice expansion for variadic functions: define the final parameter with ...T, then call the function with slice.... This page explains when that works, why it is restricted, and how to choose a practical alternative.
Concept
A function such as:
func myFunc(a, b int) int
requires exactly two separate int arguments. A slice such as []int{2, 4} is one value: it contains integers, but it is still a single slice value. Therefore, this does not compile:
arr := []int{2, 4}
myFunc(arr) // cannot use arr (variable of type []int) as int value
Go has no general-purpose operation that converts a slice into arguments for a fixed-parameter function.
Go does provide a related feature for variadic functions. A variadic function accepts zero or more values of one type:
func sum(numbers ...int) int
Inside the function, numbers behaves like a []int. At the call site, a slice can be expanded with a trailing ...:
values := []int{2, 4}
result := sum(values...)
This restriction is intentional: Go keeps function signatures explicit and statically checked. A call to a fixed-parameter function must provide the exact arguments described by its signature.
Mental Model
Think of a fixed-parameter function as a form with two separate boxes:
- box 1:
a int - box 2:
b int
A slice is a basket holding values. You cannot hand the basket to a form that asks you to fill in two individual boxes.
A variadic function is different: it has a basket-shaped final box called ...int. When you write values..., you are telling Go: “take each item from this slice and place it into the variadic basket.”
The ... expansion only works when the function explicitly has that variadic final parameter.
Syntax and Examples
Use ...T in a function declaration to make its final parameter variadic. Use slice... in a call to expand a slice into that parameter.
func sum(numbers ...int) int {
total := 0
for _, number := range numbers {
total += number
}
return total
}
func main() {
values := []int{2, 4}
result := sum(values...)
println(result) // 6
}
numbers ...int means that callers may provide any number of int values:
sum() // valid: no values
sum(2) // valid
sum(2, 4, 6) // valid
They can also provide a slice of the same element type:
values := []int{2, 4, 6}
sum(values...) // valid
A variadic parameter must be the last parameter:
func logMessage(prefix string, values ...int) {
// prefix is one required string; values is zero or more ints.
}
logMessage("scores", 10, 20)
If you must keep a fixed two-argument function, extract the elements yourself after validating the slice length:
func myFunc(a, b int) int {
return a + b
}
func main() {
arr := []int{2, 4}
if len(arr) != 2 {
panic("expected exactly two values")
}
sum := myFunc(arr[0], arr[1])
println(sum) // 6
}
Step by Step Execution
Consider this variadic example:
func sum(numbers ...int) int {
total := 0
for _, number := range numbers {
total += number
}
return total
}
func main() {
values := []int{2, 4, 8}
result := sum(values...)
println(result)
}
Execution proceeds as follows:
valuesis created as a slice containing2,4, and8.values...expands that slice for the variadic parameter.sumreceivesnumbers, which behaves as[]int{2, 4, 8}.totalstarts at0.- The loop adds
2, makingtotalequal to2. - The loop adds
4, makingtotalequal to6. - The loop adds
8, makingtotalequal to14. sumreturns14, which is assigned toresultand printed.
The important point is that values... works because sum declares numbers ...int. It would not work with func sum(a, b, c int) int.
Real World Use Cases
Variadic parameters and slice expansion are useful when a function naturally operates on a variable number of values.
-
Logging: pass a dynamic list of values to formatting or logging functions.
fmt.Println([]any{"user", 42, "signed in"}...) -
Building SQL placeholders or query fragments: pass a list of arguments to APIs designed with variadic arguments, when their parameter types match.
-
Aggregations: calculate totals, minimums, maximums, or averages from values collected in a slice.
scores := []int{82, 91, 76} total := sum(scores...) -
Configuration options: many Go libraries use variadic option functions, allowing callers to pass zero or more options.
// Illustrative API shape: // client := NewClient(WithTimeout(5*time.Second), WithRetries(3)) -
Tests: pass a generated or table-driven set of expected values to a helper that accepts a variable number of inputs.
Real Codebase Usage
In real Go code, choose the function signature based on what the operation means.
Use a slice parameter when the input is fundamentally a collection
func average(values []float64) (float64, error) {
if len(values) == 0 {
return 0, errors.New("cannot average an empty slice")
}
total := 0.0
for _, value := range values {
total += value
}
return total / float64(len(values)), nil
}
This makes the collection explicit and is often preferable for data-processing functions.
Use a variadic parameter for call-site convenience
func contains(target string, values ...string) bool {
for _, value := range values {
if value == target {
return true
}
}
return false
}
names := []string{"Ada", "Lin", "Mina"}
found := contains("Lin", names...)
The early return true is a common guard-style pattern: stop as soon as the answer is known.
Validate fixed-size data before indexing
When an operation truly needs exactly two values, keep that rule explicit:
func addPair(values []int) (int, error) {
if len(values) != 2 {
return 0, fmt.Errorf("expected 2 values, got %d", len(values))
}
return values[0] + values[1], nil
}
This avoids an index-out-of-range panic and gives callers a useful error.
Common Mistakes
Trying to pass a slice to fixed parameters
This is invalid because arr is []int, not an int:
func add(a, b int) int {
return a + b
}
arr := []int{2, 4}
add(arr) // does not compile
Use add(arr[0], arr[1]) only after checking the length, or redesign the function to accept ...int or []int.
Using ... with a non-variadic function
This is also invalid:
func add(a, b int) int {
return a + b
}
arr := []int{2, 4}
add(arr...) // does not compile: add is not variadic
The called function must declare its final parameter as ...int.
Forgetting the ... when calling a variadic function with a slice
func sum(numbers ...int) int { return 0 }
values := []int{2, 4}
sum(values) // does not compile
Write sum(values...).
Indexing without checking the slice length
values := []int{2}
result := myFunc(values[0], values[1]) // panic: index out of range
Check first when the size is not guaranteed:
if len(values) != 2 {
return
}
Mixing explicit variadic values and slice expansion
Go does not allow both forms in the same variadic argument list:
values := []int{2, 4}
// sum(1, values...) // invalid
Create a combined slice instead:
combined := append([]int{1}, values...)
result := sum(combined...)
_ = result
Expanding the wrong slice type
The expanded slice element type must match the variadic parameter type. For example, []string cannot be expanded for a ...int parameter.
Comparisons
| Situation | Function signature | Correct call | Notes |
|---|---|---|---|
| Exactly two required integers | func add(a, b int) int | add(2, 4) | No automatic slice unpacking exists. |
| Exactly two values stored in a slice | func add(a, b int) int | add(values[0], values[1]) | Validate len(values) first if needed. |
| Any number of integers | func sum(values ...int) int | sum(2, 4, 6) | Convenient for direct values. |
| Existing integer slice for a variadic function | func sum(values ...int) int | sum(numbers...) | The trailing ... expands the slice. |
| A collection is the main input | func process(values []int) | process(numbers) | Often clearer for data-processing APIs. |
A variadic parameter is represented as a slice inside the function, so these two implementations process data similarly:
func sumVariadic(values ...int) int { return 0 }
func sumSlice(values []int) int { return 0 }
The primary difference is the calling syntax: variadic functions accept separate values and can accept an expanded slice; slice functions always receive one slice.
Cheat Sheet
// Fixed parameters: requires separate arguments.
func add(a, b int) int { return a + b }
values := []int{2, 4}
result := add(values[0], values[1]) // validate len(values) when necessary
// Variadic parameter: must be last and has the form ...T.
func sum(values ...int) int {
return 0
}
sum(2, 4) // separate arguments
sum() // zero arguments is valid
sum(values...) // expand a []int
- Go cannot generally unpack a slice for a fixed-parameter function.
slice...is valid only for a matching variadic parameter.- Inside
func f(values ...T),valuesbehaves as a[]T. - A variadic parameter must be the final parameter.
- Check
len(slice)before using fixed indexes such asslice[0]andslice[1]. - The slice element type must match the variadic element type.
FAQ
Can Go unpack a slice into a fixed number of function parameters?
No. Go has no equivalent of Python's f(*values) for ordinary fixed-parameter functions. Extract individual elements after validating the slice length, or change the API design.
What does slice... mean in Go?
It expands a slice into arguments for the final variadic parameter of a function. For example, sum(values...) passes a []int to func sum(...int).
Can I use ... with any function?
No. The target function must have a variadic final parameter, such as func printAll(values ...string).
Is a variadic parameter a slice in Go?
Inside the function, yes. A parameter declared as values ...int can be iterated, indexed, and passed to functions as a []int.
Should I use []T or ...T in my function API?
Use []T when the function conceptually accepts a collection. Use ...T when accepting separate values is convenient and supporting slice... is useful for callers.
Can I pass values before an expanded slice to a variadic function?
Not directly in the same call. Combine the values into one slice with append, then expand that slice.
Does slice expansion copy the slice?
The language allows the callee to treat the variadic parameter as a slice. Do not rely on expansion to create an independent copy; make a copy explicitly if your code needs isolation.
Mini Project
Description
Build a small score utility that accepts any number of integer scores. It demonstrates a variadic function, calling it with direct values, and calling it with a slice using ....
Goal
Calculate and print the total and average of a set of scores supplied through a slice.
Requirements
Define a variadic function that accepts zero or more integer scores.
Return an error when no scores are provided.
Calculate the total and average score.
Create a slice containing at least three scores.
Call the variadic function by expanding the slice with scores....
Print the calculated total and average.
Keep learning
Related questions
Are Go Maps Passed by Value or Reference?
Learn Go map parameter and return semantics: maps are passed by value, while copies share underlying data. Know when map pointers are useful.
Authentication for Go REST APIs: Sessions, Tokens, and Middleware
Learn practical authentication patterns for Go REST APIs, including password hashing, server sessions, bearer tokens, middleware, and security rules.
Automatic Build Versioning in Go: Embed Incrementing Build Numbers
Learn how to add automatic build versioning in Go using linker flags, build metadata, CI counters, and Git-based version values.