Question
In Go, a value of type T can be assigned to an interface{} value when T implements that interface. However, a slice like []T cannot be passed directly to a function that expects []interface{}.
For example:
package main
func foo(items []interface{}) {
// do something
}
func main() {
a := []string{"hello", "world"}
foo(a)
}
This produces the error:
cannot use a (type []string) as type []interface{} in function argument
Even an explicit conversion does not work:
b := []interface{}(a)
That also fails with:
cannot convert a (type []string) to type []interface{}
To work around this, it is common to create a new slice and copy each element:
b := make([]interface{}, len(a))
for i := range a {
b[i] = a[i]
}
Why does Go behave this way? Is there a non-trivial reason that []T is not convertible to []interface{}? Also, is there a better or more standard way to handle this kind of conversion?
Short Answer
By the end of this page, you will understand why Go allows T to become interface{} but does not allow []T to become []interface{}. You will learn the memory-layout reason behind this rule, how to perform the conversion correctly, when a manual loop is necessary, and how real Go code usually avoids this problem through better API design.
Concept
In Go, an individual value and a slice of values are very different things.
A value like string can be stored inside an interface{} because an interface value is designed to hold:
- the concrete type information
- the concrete value
So this is valid:
var s string = "hello"
var x interface{} = s
But a slice is not "a bag of individually converted values." A slice is a small descriptor pointing to an underlying array. The element type of that array matters.
[]stringpoints to an array ofstringvalues[]interface{}points to an array ofinterface{}values
Those are different underlying array layouts.
That means Go cannot safely reinterpret one as the other.
Why this matters
If Go allowed []string to be used as []interface{} directly, code inside the function could try to store a non-string value into that slice:
func foo(items []interface{}) {
items[0] = 123
}
If items were actually backed by a []string, this would break type safety immediately.
So the rule protects both:
- memory correctness
- type safety
The key idea
This works:
T -> interface{}
This does not work:
[]T -> []interface{}
because Go does not do element-by-element conversion automatically for slices.
Why a loop is required
To create a []interface{}, Go must create a new slice whose elements are actual interface values, then copy each original element into that new slice.
That is exactly what your loop does:
b := make([]interface{}, len(a))
for i := range a {
b[i] = a[i]
}
This is not just busywork. It is creating a new correctly typed slice with a different element representation.
Is there a standard library helper?
For ordinary Go, there is no built-in automatic conversion from []T to []interface{}. Writing the loop is the standard approach.
In newer Go versions with generics, many APIs can be designed to avoid needing []interface{} at all. That is often the better solution.
Mental Model
Think of a slice as a tray with slots of a specific shape.
- A
[]stringis a tray where every slot is shaped forstring - A
[]interface{}is a tray where every slot is shaped forinterface{}
Even though you can place one string into one interface{} box, that does not mean a whole tray of string-shaped slots can suddenly become a tray of interface-shaped slots.
To make that happen, you must build a new tray and place each item into its new slot one by one.
That is why the loop is necessary.
Syntax and Examples
Core syntax
Converting individual values to interface{}:
var s string = "hello"
var v interface{} = s
Converting a slice requires manual copying:
strings := []string{"hello", "world"}
values := make([]interface{}, len(strings))
for i, s := range strings {
values[i] = s
}
Example: calling a function that expects []interface{}
package main
import "fmt"
func printAll(items []interface{}) {
for _, item := range items {
fmt.Println(item)
}
}
func main() {
words := []string{"Go", "is", "simple"}
items := make([]interface{}, len(words))
for i, w := range words {
items[i] = w
}
printAll(items)
}
What this does
- creates a
[]string - creates a new
[]interface{}of the same length - copies each string into the interface slice
- passes the new slice to the function
Variadic function note
A common related case is fmt.Println or fmt.Sprint, which accept ...interface{}.
This does not mean you can pass []string directly with ...:
words := []string{"a", "b"}
// fmt.Println(words...) // invalid
You still need a []interface{} first:
items := make([]interface{}, len(words))
for i, w := range words {
items[i] = w
}
fmt.Println(items...)
Step by Step Execution
Consider this example:
package main
import "fmt"
func main() {
words := []string{"hello", "world"}
items := make([]interface{}, len(words))
for i, w := range words {
items[i] = w
}
fmt.Println(items)
}
Step-by-step
1. Create the original slice
words := []string{"hello", "world"}
Now words is a slice whose underlying array stores two string values.
2. Create a new destination slice
items := make([]interface{}, len(words))
Now items is a different slice with a different underlying array. Its elements are interface{} values.
3. Loop through the original slice
for i, w := range words {
items[i] = w
}
Iteration details:
- first iteration:
i = 0w = "hello"items[0] = "hello"converts that string into an interface value
- second iteration:
i = 1w = "world"items[1] = "world"
4. Print the result
fmt.Println(items)
Output:
[hello world]
Important detail
After conversion, words and items are separate slices backed by different arrays. Modifying one does not modify the other in the same way.
Real World Use Cases
1. Logging and formatted output
Some APIs accept values as interface{} so they can handle mixed types. If your data starts as []string or []int, you may need to convert it before passing it to a generic formatter.
2. Generic containers in older Go code
Before generics, many reusable utilities used interface{} to accept any type. Converting slices into []interface{} was a common pattern.
3. Database or serialization helpers
Some helpers expect []interface{} for parameter lists, especially when building query arguments dynamically.
Example:
args := []interface{}{id, name, active}
If those values originally came from typed slices, they must be copied in.
4. Reflection-based libraries
Libraries that use reflection often expose APIs based on interface{}. Typed slices cannot be passed directly unless the library accepts any and handles the slice internally.
5. Variadic APIs
Functions with ...interface{} are common in Go, such as formatting and logging functions. Passing a typed slice to them often requires conversion first.
Real Codebase Usage
In real Go projects, developers usually try to avoid requiring []interface{} unless it is truly necessary.
Common patterns
1. Prefer typed parameters
Instead of writing:
func foo(items []interface{})
prefer:
func foo(items []string)
if the function really only works with strings.
This is clearer, safer, and avoids conversions.
2. Use variadic parameters when appropriate
For APIs meant to accept arbitrary values:
func logValues(values ...interface{})
Callers can pass mixed values directly:
logValues("user", 42, true)
But a typed slice like []string still must be converted if expanded.
3. Use generics in modern Go
A generic function can often replace an old interface{}-based API:
func printSlice[T any](items []T) {
for _, item := range items {
fmt.Println(item)
}
}
Then both of these work without conversion:
printSlice([]string{"a", "b"})
printSlice([]int{1, 2, 3})
4. Build helper functions when conversion is unavoidable
If your codebase needs this often, wrap the loop:
func toInterfaces[T any](items []T) []interface{} {
result := make([]interface{}, len(items))
for i, v := range items {
result[i] = v
}
return result
}
This keeps calling code cleaner.
5. Use guard clauses for nil or empty slices
func toInterfaces[T any](items []T) []interface{} {
if len(items) == 0 {
return nil
}
result := make([]interface{}, len(items))
for i, v := range items {
result[i] = v
}
return result
}
This kind of small helper is common in production code.
Common Mistakes
Mistake 1: Assuming element conversion implies slice conversion
Broken code:
words := []string{"a", "b"}
var items []interface{} = words
Why it fails:
stringcan becomeinterface{}[]stringcannot become[]interface{}automatically
Fix:
items := make([]interface{}, len(words))
for i, w := range words {
items[i] = w
}
Mistake 2: Trying an explicit cast
Broken code:
items := []interface{}(words)
Why it fails:
Go does not support this conversion because the slice element types have different representations.
Mistake 3: Forgetting that ...interface{} has the same issue
Broken code:
words := []string{"hello", "world"}
fmt.Println(words...)
Why it fails:
words... only works when words is already of type []interface{} for a function expecting ...interface{}.
Mistake 4: Using []interface{} everywhere unnecessarily
Beginners sometimes design APIs like this:
func process(items []interface{})
when this is better:
func process(items []string)
If the function only needs one concrete type, use that type.
Mistake 5: Assuming the conversion is free
The manual conversion allocates a new slice and copies elements. If done frequently in performance-sensitive code, this cost matters.
Avoid repeated conversion in hot paths when possible.
Comparisons
| Concept | What it means | Allowed automatically? | Why |
|---|---|---|---|
T -> interface{} | One concrete value stored in an interface | Yes | Interface values are designed for this |
[]T -> []interface{} | A whole typed slice treated as interface slice | No | Underlying element layouts differ |
[]T -> []any | Same as above (any is alias for interface{}) | No | Same rule |
[]string -> []byte | Convert string slice to byte slice | No | Different element types and meaning |
string -> []byte | Convert one string to bytes | Yes, with explicit conversion | Defined conversion for individual values |
interface{} vs generics
| Approach | Best for | Pros | Cons |
|---|---|---|---|
interface{} / any | APIs that truly accept mixed types | Flexible | Loses type information, often needs assertions |
| Generics | Functions that work on many types uniformly | Type-safe, no per-call conversion to []interface{} | Requires Go with generics |
[]interface{} vs typed slices
| Slice type | Good for | Trade-off |
|---|---|---|
[]string | Only strings | Strong typing, simpler code |
[]int | Only integers | Strong typing, better performance clarity |
[]interface{} | Mixed values or reflection-heavy APIs | More flexible, less type-safe |
Cheat Sheet
Quick rules
Tcan be assigned tointerface{}ifTimplements the interface.[]Tcannot be assigned or converted to[]interface{}directly.anyis just an alias forinterface{}.- Converting
[]Tto[]interface{}requires a new slice and a loop.
Standard conversion pattern
result := make([]interface{}, len(items))
for i, v := range items {
result[i] = v
}
Generic helper
func toInterfaces[T any](items []T) []interface{} {
result := make([]interface{}, len(items))
for i, v := range items {
result[i] = v
}
return result
}
Why direct conversion fails
[]stringstores strings[]interface{}stores interface values- these have different internal layouts
Variadic reminder
For func f(args ...interface{}), this works:
f("a", 1, true)
This does not work:
words := []string{"a", "b"}
// f(words...)
Convert first if needed.
Best practice
- Prefer typed slices when possible
- Use
[]interface{}only when you truly need mixed types - Prefer generics for reusable typed operations
FAQ
Why can Go convert string to interface{} but not []string to []interface{}?
Because a single value conversion is different from reinterpreting an entire slice. The two slice types use different element representations in memory.
Is []any different from []interface{}?
No. any is just an alias for interface{} in Go. []any and []interface{} are the same type.
Is there a built-in function to convert []T to []interface{}?
No general built-in conversion exists. The usual approach is to allocate a new slice and copy elements in a loop.
Is the manual conversion expensive?
It has a cost: a new slice allocation plus per-element assignment. For small slices this is usually fine, but it can matter in performance-sensitive code.
Can generics solve this problem?
Often, yes. Instead of accepting []interface{}, a generic function can accept []T, which avoids the conversion entirely.
Why doesn't Go do the conversion automatically?
Automatic conversion would require allocating a new slice and copying every element. Go avoids hidden allocations and keeps conversions explicit.
Can I use reflection to avoid the loop?
You can, but it is usually slower, more complex, and less readable. A normal loop is the standard and preferred solution.
Mini Project
Description
Build a small utility that prints slices of different types in a generic-looking way. This project demonstrates the practical issue behind converting []T to []interface{} and shows both the classic conversion approach and a better generic approach.
Goal
Create a Go program that can print string and integer slices, first by converting them to []interface{}, and then by using a generic helper that avoids the conversion problem.
Requirements
- Create a function that accepts
[]interface{}and prints each item. - Write code to convert a
[]stringinto[]interface{}before calling that function. - Write code to convert a
[]intinto[]interface{}before calling that function. - Add a generic function that prints any slice without converting it to
[]interface{}. - Demonstrate both approaches in
main.
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.