Variadic Functions in Go
1. What is a variadic function?
A function that accepts any number of arguments of the same type.
Syntax:
func sum(numbers ...int) int { // numbers becomes []int inside function
total := 0
for _, num := range numbers {
total += num
}
return total
}2. How does Go implement variadic functions?
Go converts arguments into a slice.
foo(1, 2, 3)→values := []int{1, 2, 3}- If no args:
valuesis an empty slice (notnil).
3. Can we pass a slice to a variadic function?
Yes, with slice... syntax:
nums := []int{1, 2, 3}
foo(nums...) // Equivalent to foo(1, 2, 3)4. Can a variadic function handle multiple types?
Not directly, but use interface{} (loses type safety):
func handle(vals ...interface{}) {
for _, v := range vals {
switch v.(type) {
case int: // handle int
case string: // handle string
// ...
}
}
}5. Can we have multiple variadic parameters?
No. Rules:
- Only one variadic parameter per function.
- Must be the last parameter.
Example:
func greet(prefix string, names ...string) { } // Valid
func fail(a ...int, b ...int) { } // Compile error6. What if no arguments are passed?
The parameter becomes an empty slice ([]T{}), not nil. Always check length:
func safeSum(nums ...int) int {
if len(nums) == 0 {
return 0 // Handle empty case
}
// ... sum logic ...
}7. Key advantages of variadic functions?
- Avoids writing overloaded functions (e.g.,
Sum1(int),Sum2(int, int)). - Cleaner APIs (e.g.,
fmt.Println()). - Flexible for helper functions (e.g., joining strings).
8. Key limitations?
- Single type only (without
interface{}hacks). - Slice allocation overhead for each call.
- No compile-time checks for minimum arguments.
9. Practical example: String joiner
func Join(sep string, strs ...string) string {
return strings.Join(strs, sep)
}
// Usage:
Join(", ", "A", "B", "C") // "A, B, C"10. Anti-patterns to avoid
- Unchecked
interface{}:func risky(vals ...interface{}) { n := vals[0].(int) // Panics if not int! } - Hidden allocations (variadic calls create new slices).
- Ignoring empty args: Always handle
len(values) == 0.
Memory Tricks
...= “unpack” (when passing slices) or “variable” (in declaration).- Slice inside: Think of
...Tas[]Tin the function body. - Last parameter only: Like a “greedy” argument collector.
Advanced Patterns
Mixed Parameter Types
func process(prefix string, values ...interface{}) {
// Can handle multiple types via empty interface
// Requires type assertion for actual usage
}Forwarding Arguments
func log(values ...interface{}) {
fmt.Println(values...)
}Pro Tips for Interviews
-
Memory Efficiency:
- Each variadic call creates a new slice
- For performance-critical code, consider passing a pre-allocated slice
-
Error Handling:
- Variadic functions can’t enforce argument count
- Document minimum requirements clearly
-
Testing Edge Cases:
- Always test with: zero, one, and many arguments
- Test with slice unpacking
Practical Example
func join(sep string, strs ...string) string {
if len(strs) == 0 {
return ""
}
return strings.Join(strs, sep)
}
// Usage:
join(", ", "a", "b", "c") // "a, b, c"
words := []string{"hello", "world"}
join(" ", words...) // "hello world"Anti-Patterns to Avoid
-
Hidden Allocations:
// This creates a new slice allocation each call func badIdea(values ...int) { // ... } -
Type Safety Violations:
// Avoid unconstrained interface{} without type checking func risky(values ...interface{}) { // Might panic if wrong types passed }
Key Characteristics to Remember
-
Single Variadic Parameter Rule:
- Only one variadic parameter allowed per function
- Must be the last parameter in the signature
-
Behind the Scenes:
- Go converts variadic arguments into a slice
- When no arguments passed, it’s an empty slice (not nil)
len=0
-
Slice Unpacking:
- Pass existing slices using
slice...syntax - Works for both array slices and slice literals
- Pass existing slices using
nums := []int{1, 2, 3}
sum(nums...) // Equivalent to sum(1, 2, 3)