Question

Understanding interface{} in Go: The Empty Interface

Question

I am new to Go interfaces and am working with SOAP requests. What does interface{} mean in this struct?

type Envelope struct {
    Body `xml:"soap:"`
}

type Body struct {
    Msg interface{}
}

I have also seen similar syntax in functions such as fmt.Println. What does an empty interface, interface{}, achieve in Go?

Short Answer

interface{} is Go's empty interface. A value of this type can hold a value of any concrete type because every Go type implements an interface with zero required methods. You will learn how to assign values to it, retrieve their original types safely, and recognize when a more specific type is a better design.

Concept

In Go, an interface describes behavior through a set of methods. For example:

type Stringer interface {
    String() string
}

Only types with a String() string method satisfy Stringer.

An empty interface has no required methods:

interface{}

Because there are no methods to implement, every Go type satisfies interface{}. It can store an int, string, struct, slice, pointer, function, or another interface.

var value interface{}

value = 42
value = "hello"
value = []string{"red", "blue"}

At runtime, an interface value conceptually carries two pieces of information:

  1. The concrete type currently stored, such as int or string.
  2. The concrete value itself.

In your SOAP-related structure:

type Body struct {
    Msg interface{}
}

Msg can hold different message structs. This can be useful when a SOAP body may contain one of several possible XML payload types. However, the code that reads Msg must determine which concrete type was stored.

In modern Go, any is an alias for interface{}:

var value any = "hello"

any and interface{} mean exactly the same thing. Many new Go codebases prefer any because it is easier to read.

Mental Model

Think of a normal variable as a labelled box that accepts one kind of item:

var age int

The age box accepts integers only.

An interface{} variable is a flexible box with no restriction on what kind of Go value it can hold:

var item interface{}

You can put an integer in it now and a string later. But when you take something out, you need to check what it is before treating it as a particular type.

The box remembers the item's type, but the variable's declared type is still interface{}. Go therefore does not let you use it as an int, string, or custom struct until you inspect or assert its concrete type.

Take Quiz

Syntax and Examples

Declare an empty-interface variable with either interface{} or any:

var first interface{}
var second any

Assign different concrete values:

package main

import "fmt"

func main() {
    var value interface{}

    value = 25
    fmt.Println(value)

    value = "Go"
    fmt.Println(value)

    value = true
    fmt.Println(value)
}

fmt.Println can print each value because it accepts values of any type. Its simplified signature is:

func Println(a ...any) (n int, err error)

The ... means it accepts zero or more arguments, and any means each argument may have any concrete type:

fmt.Println("status", 200, true)

Reading a concrete value with a type assertion

Use a type assertion when you expect a specific type:

var value any = "SOAP request"

text, ok := value.(string)
if ok {
    fmt.Println("Message:", text)
}

The ok result is true only when value currently contains a string. This form is safe because it does not panic when the type differs.

Storing a custom message

type LoginRequest struct {
    Username string
}

body := Body{
    Msg: LoginRequest{Username: "ada"},
}

request, ok := body.Msg.(LoginRequest)
if ok {
    fmt.Println(request.Username)
}

Step by Step Execution

Consider this program:

package main

import "fmt"

func main() {
    var message interface{} = 200

    number, ok := message.(int)
    fmt.Println(number, ok)

    text, ok := message.(string)
    fmt.Println(text, ok)
}

Step by step:

  1. message is declared as interface{} and stores the concrete value 200.
  2. Its current concrete type is int.
  3. message.(int) succeeds, so number becomes 200 and ok becomes true.
  4. The first Println outputs 200 true.
  5. message.(string) does not match because the stored value is an int, not a string.
  6. The safe assertion returns the zero value for string ("") and false.
  7. The second Println outputs false.

A direct assertion without ok panics if it is wrong:

text := message.(string) // panic: interface conversion: interface {} is int, not string

Use the two-result form unless a mismatch is truly impossible.

Real World Use Cases

Common uses of interface{} or any include:

  • Formatting and logging: Functions such as fmt.Println, fmt.Printf, and many logging libraries accept values of many types.
  • Decoded JSON: When decoding JSON into an unknown shape, a program may use map[string]any or any.
  • Generic event payloads: An event system may carry different payload structs for different event names.
  • Plugin or extension points: A framework may receive values whose concrete types are chosen by callers.
  • SOAP/XML message containers: A body that can hold several possible request or response structs may temporarily use an interface field.

For a SOAP message, a more concrete design is often easier when the body format is known:

type Envelope struct {
    Body Body `xml:"soap:Body"`
}

type Body struct {
    Msg LoginRequest `xml:"LoginRequest"`
}

Use any only when Msg genuinely needs to hold multiple possible types.

Take Quiz

Real Codebase Usage

In production Go code, empty interfaces are used carefully. They are useful at boundaries where the shape of data is unknown, but specific types are preferred inside business logic.

Validate unknown input with a type switch

A type switch handles several possible concrete types:

func describe(value any) string {
    switch v := value.(type) {
    case string:
        return "text: " + v
    case int:
        return fmt.Sprintf("number: %d", v)
    case nil:
        return "no value"
    default:
        return fmt.Sprintf("unsupported type: %T", v)
    }
}

Use a behavior-focused interface when possible

If your code only needs to write text, do not accept any; ask for the behavior you require:

func save(w io.Writer, text string) error {
    _, err := fmt.Fprintln(w, text)
    return err
}

io.Writer is better than any here because it documents the requirement and lets the compiler verify it.

Keep type assertions near the boundary

For example, after parsing loosely typed input, convert it to a well-defined struct early. This prevents any from spreading through the rest of the application.

type Config struct {
    Port int
}

func readPort(raw any) (int, error) {
    port, ok := raw.(int)
    if !ok {
        return 0, fmt.Errorf("port must be an int, got %T", raw)
    }
    return port, nil
}

Common Mistakes

Assuming interface{} has the methods of its stored value

This does not compile:

var value any = "hello"
fmt.Println(value.ToUpper()) // invalid: value has type any

The declared type is any, which has no methods. Assert the concrete type first, then use its operations:

text, ok := value.(string)
if ok {
    fmt.Println(strings.ToUpper(text))
}

Using an unchecked type assertion

This can panic:

var value any = 10
text := value.(string)

Prefer the comma-ok form:

text, ok := value.(string)
if !ok {
    fmt.Println("value is not a string")
    return
}
fmt.Println(text)

Using any when a concrete type is known

Avoid vague fields when the payload always has one shape:

type Body struct {
    Msg any
}

If it is always a LoginRequest, make that explicit:

type Body struct {
    Msg LoginRequest
}

This gives compile-time checking and clearer code.

Forgetting that typed nil is different from a nil interface

var pointer *int = nil
var value any = pointer

fmt.Println(value == nil) // false

value is not a nil interface: it contains a value whose concrete type is *int, even though that pointer is nil. This matters in error handling and optional values.

Expecting XML or JSON libraries to infer every interface value automatically

An interface field may need extra type information, tags, or custom marshal/unmarshal code depending on the format and possible payload types. When the XML schema is known, concrete fields are usually simpler and safer.

Comparisons

Featureinterface{} / anySpecific interfaceConcrete type
Can storeAny Go valueValues with required methodsOne specified type
Compile-time guaranteesLowVerifies behaviorStrongest
Need type assertions to access concrete dataOftenSometimesNo
Best useUnknown or heterogeneous dataDepend on behaviorKnown data shape
Examplefmt.Println(value)io.Writerstring, LoginRequest

any versus interface{}

They are identical:

var a any = 1
var b interface{} = 1

any was added as a readability alias. Older code and APIs commonly use interface{}.

Empty interface versus a non-empty interface

type Printer interface {
    Print()
}

Printer accepts only types with a Print method. In contrast, any accepts every type, including int and string.

Choose a non-empty interface when your function needs behavior. Choose a concrete type when the data shape is known.

Take Quiz

Cheat Sheet

// `any` and `interface{}` are the same type.
var value interface{} = 42
var other any = "hello"

// Assign any concrete Go value.
value = true
value = []int{1, 2, 3}

// Safe type assertion.
text, ok := value.(string)

// Type switch for multiple possible types.
switch v := value.(type) {
case string:
    fmt.Println(v)
case int:
    fmt.Println(v)
default:
    fmt.Printf("unknown type: %T\n", v)
}

// Variadic function that accepts any number of values of any type.
func log(values ...any) {
    fmt.Println(values...)
}
  • Every Go type implements interface{} because it requires zero methods.
  • Prefer any in new code when it improves readability; both forms are equivalent.
  • Prefer concrete types when the value's shape is known.
  • Prefer a specific interface, such as io.Reader, when you need behavior.
  • Use value.(T) only when you are certain of T; otherwise use result, ok := value.(T).
  • A non-nil interface can contain a typed nil pointer.

FAQ

What does interface{} mean in Go?

It is the empty interface: a type that can hold a value of any Go type. In current Go, any is an exact alias for it.

Why can interface{} hold every type?

Interfaces require types to implement their listed methods. The empty interface lists no methods, so every type satisfies it.

Is any different from interface{}?

No. any is an alias added to make code easier to read. They compile and behave identically.

Why does fmt.Println accept strings, numbers, and structs?

Its parameters use ...any, meaning it accepts any number of arguments and every argument may be any concrete type.

How do I convert an interface{} to a string in Go?

Use a type assertion:

text, ok := value.(string)

Check ok before using text if the type may differ.

Can a type assertion panic?

Yes. value.(string) panics if value does not contain a string. The two-result form, text, ok := value.(string), avoids that panic.

Should struct fields use interface{}?

Only when they truly need to hold several unrelated types. If the field has a known shape, use a concrete type for safer and clearer code.

What does interface{}(nil) mean?

It is a nil interface with no concrete type and no value. It differs from an interface that contains a typed nil pointer.

Take Quiz

Mini Project

Description

Build a small event formatter. It receives values stored as any, identifies their concrete types with a type switch, and returns readable messages. This resembles code at an application boundary, such as logging, event processing, or decoding flexible input.

Goal

Create a function that safely formats string, integer, and login-event values passed as any.

Requirements

Accept one value of type any.

Take Quiz

Keep learning

Related questions

View more