The Problem
It is a common trap: you have a slice of strings and a function that accepts []interface{}. Since a single string fits perfectly into an interface{}, you assume the slice version should work too. However, the Go compiler will block this every time.
You might hit this wall when passing data to a database driver, a JSON encoder, or a custom logging wrapper. Here is the snippet that usually triggers the frustration:
package main
import "fmt"
func process(items []interface{}) {
for _, item := range items {
fmt.Println(item)
}
}
func main() {
data := []string{"apple", "banana", "cherry"}
// This triggers the error:
// cannot use data (type []string) as type []interface {} in argument to process
process(data)
}
Why Go Rejects This
Go is famous for its "no magic" philosophy. While a string satisfies the interface{}, the memory layout of a []string is fundamentally different from a []interface{}.
Think of it this way: on a 64-bit system, a string header is 16 bytes (a pointer to the data and a length). An interface{} is also 16 bytes, but it consists of a type pointer and a data pointer. Because the internal structures differ, Go cannot simply treat one as the other.
If Go performed this conversion automatically, it would have to loop through your entire slice and reallocate every element. This is an O(n) operation. To keep performance predictable, Go forces you to make this cost explicit in your code rather than hiding it behind a simple assignment.
Solution 1: The "For Loop" Method (Standard)
The most direct fix is to manually allocate a new slice and copy the elements. This makes the performance tax—allocating new memory and spending CPU cycles—obvious to anyone reading your code.
func main() {
data := []string{"apple", "banana", "cherry"}
// Allocate a new slice of the correct type
interfaceSlice := make([]interface{}, len(data))
for i, v := range data {
interfaceSlice[i] = v
}
process(interfaceSlice)
}
Solution 2: Variadic Functions
If you can change the function signature, use a variadic parameter (...interface{}). This allows you to pass elements individually. While it doesn't bypass the conversion, it often leads to cleaner code at the call site when dealing with small sets of data.
// Use ...interface{} instead of []interface{}
func process(items ...interface{}) {
for _, item := range items {
fmt.Println(item)
}
}
func main() {
data := []string{"apple", "banana", "cherry"}
// You still need to convert the slice manually
vals := make([]interface{}, len(data))
for i, v := range data {
vals[i] = v
}
// Pass elements using the spread operator
process(vals...)
}
Solution 3: The Modern Way (Generics)
If you are using Go 1.18 or newer, stop using []interface{} for this. Generics are the superior choice. Instead of forcing your data into a generic interface container, let the function accept a slice of any type T.
// Generic function that accepts a slice of any type T
func process[T any](items []T) {
for _, item := range items {
fmt.Println(item)
}
}
func main() {
data := []string{"apple", "banana", "cherry"}
nums := []int{10, 20, 30}
// No manual conversion needed!
process(data)
process(nums)
}
Verification and Performance
- **Compile check:** Run `go build`. If the error disappears, your types are correctly aligned.
- **Memory check:** If you are converting a slice with 1,000,000 strings, Solution 1 will allocate roughly 16MB of new memory for the interface slice. Use a benchmark if this happens in a high-frequency loop.
- **Type Safety:** Solution 3 (Generics) is the safest. It preserves the underlying type information, whereas `interface{}` loses it until you perform a type assertion.
Key Takeaways
- Slices are not covariant. `[]T` is never `[]interface{}`, regardless of what `T` is.
- Explicit conversion is a Go feature, not a bug. It prevents hidden O(n) performance hits.
- Generics (`[T any]`) should be your first choice for writing flexible functions in modern Go projects.

