Code of the Day
IntermediateInterfaces & errors

Type assertions

Recover the concrete type from an interface value safely with assertions and type switches.

GoIntermediate10 min read
Recommended first
By the end of this lesson you will be able to:
  • Use x.(T) to assert an interface value to a concrete type
  • Apply the comma-ok idiom to avoid panics on failed assertions
  • Write a type switch to branch on the concrete type of an interface value
  • Decide when a type switch is preferable to a series of if statements

Interfaces let you treat many concrete types uniformly. Sometimes you need to go the other direction: given an interface value, you want the concrete type back so you can call methods specific to that type. Go provides two tools for this — type assertions and type switches.

The x.(T) assertion

A extracts the concrete value from an interface:

var s Stringer = Point{3, 4}
p, ok := s.(Point)   // comma-ok form — safe
if ok {
    fmt.Println(p.X, p.Y)   // 3 4
}

If the assertion succeeds, p holds the concrete Point value and ok is true. If the underlying type is not Point, ok is false and p is the zero value of Point — no panic.

You can also use the single-return form, but it panics if the assertion fails:

p := s.(Point)   // panics if s doesn't hold a Point

Prefer the comma-ok form in production code. The single-return assertion is only safe when you are certain of the type — for example, immediately after a type switch arm. A panic from a failed assertion in a server means a 500 and a crash log.

The type switch

When you need to branch on several possible types, a is clearer than a chain of assertions:

func describe(i interface{}) string {
    switch v := i.(type) {
    case int:
        return fmt.Sprintf("int: %d", v)
    case string:
        return fmt.Sprintf("string: %q", v)
    case Point:
        return fmt.Sprintf("Point at (%g, %g)", v.X, v.Y)
    default:
        return fmt.Sprintf("unknown type: %T", v)
    }
}

Inside each case, the variable v has the concrete type for that arm — so inside case int, v is an int and you can do arithmetic on it directly.

The %T verb in the default case prints the concrete type name, which is useful for debugging unknown interface values.

Interface assertions (checking interface satisfaction)

You can also assert that an interface value implements a second interface:

type Saver interface {
    Save() error
}

if saver, ok := someWriter.(Saver); ok {
    saver.Save()
}

This is how optional capabilities are discovered at runtime: accept a broad interface (Writer), check whether it also has the extra capability (Saver), and use it if available.

When to use type assertions vs generics

Type assertions work on interface values at runtime. They are appropriate when:

  • You have data arriving as interface{} (e.g., from JSON decoding or a heterogeneous collection).
  • You want to check optional capabilities of a value.

Go 1.18 introduced generics for a different use case: writing functions and data structures parameterised over a type at compile time. If you find yourself writing a type switch that handles int, float64, string and then calling the same operation on each — that's a sign generics might be cleaner. For control flow that genuinely differs per type, a type switch remains the right tool.

A type switch over error is particularly idiomatic in Go — the errors.As function (covered in the next lesson) builds on the same idea and handles error chain walking for you.

Check your understanding

Knowledge check

  1. 1.
    What happens when you use the single-return form p := s.(Point) and s does not hold a Point?
  2. 2.
    Inside a type switch case arm, the variable v has the concrete type for that arm, not the interface type.

Do it yourself

Write a function printAny(vs []interface{}) that uses a type switch to print each element with a label: "int: 42", "string: hello", "bool: true", or "other" for unknown types. Call it with a mixed slice.

go run main.go

Where to go next

With type assertions in your toolkit, the next lesson explores Go's error interface — the idiomatic way to signal and handle failure.

Finished reading? Mark it complete to track your progress.

On this page