Question

Why Go Cannot Convert []T to []interface{} Implicitly

gogo-reflectgo-interface

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.

  • []string points to an array of string values
  • []interface{} points to an array of interface{} 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 []string is a tray where every slot is shaped for string
  • A []interface{} is a tray where every slot is shaped for interface{}

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.

Take Quiz

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 = 0
    • w = "hello"
    • items[0] = "hello" converts that string into an interface value
  • second iteration:
    • i = 1
    • w = "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.

Take Quiz

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:

  • string can become interface{}
  • []string cannot 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

ConceptWhat it meansAllowed automatically?Why
T -> interface{}One concrete value stored in an interfaceYesInterface values are designed for this
[]T -> []interface{}A whole typed slice treated as interface sliceNoUnderlying element layouts differ
[]T -> []anySame as above (any is alias for interface{})NoSame rule
[]string -> []byteConvert string slice to byte sliceNoDifferent element types and meaning
string -> []byteConvert one string to bytesYes, with explicit conversionDefined conversion for individual values

interface{} vs generics

ApproachBest forProsCons
interface{} / anyAPIs that truly accept mixed typesFlexibleLoses type information, often needs assertions
GenericsFunctions that work on many types uniformlyType-safe, no per-call conversion to []interface{}Requires Go with generics

[]interface{} vs typed slices

Slice typeGood forTrade-off
[]stringOnly stringsStrong typing, simpler code
[]intOnly integersStrong typing, better performance clarity
[]interface{}Mixed values or reflection-heavy APIsMore flexible, less type-safe
Take Quiz

Cheat Sheet

Quick rules

  • T can be assigned to interface{} if T implements the interface.
  • []T cannot be assigned or converted to []interface{} directly.
  • any is just an alias for interface{}.
  • Converting []T to []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

  • []string stores 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.

Take Quiz

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 []string into []interface{} before calling that function.
  • Write code to convert a []int into []interface{} before calling that function.
  • Add a generic function that prints any slice without converting it to []interface{}.
  • Demonstrate both approaches in main.
Take Quiz

Keep learning

Related questions

View more