Type Assert and Convert at Runtime

Updated

September 8, 2026

Type Assert and Convert at Runtime

Overview

Type assertions and conversions are not only compile-time checks. Failed asserts panic (or return ok=false); interface asserts consult dynamic type metadata in the interface word.

Diagram: assert path

  interface value
       │
       ├── i.(T) ok-form ──► value or ok=false
       ├── i.(T) bare    ──► value or panic
       └── type switch   ──► branch on dynamic type
var i any = "x"
s := i.(string)      // panic if wrong
s, ok := i.(string)  // safe
switch v := i.(type) { case string: _ = v }

Convert vs assert

Convert T(x) Assert i.(T)
Static types both known interface → concrete/interface
Failure compile error panic or ok=false
Cost often pure codegen type metadata compare

Interface → interface

var r io.Reader = bytes.NewBuffer(nil)
rc, ok := r.(io.ReadCloser) // may fail

Runtime checks method set / itab compatibility for the pair of interface types.

Performance notes

  • Hot asserts on varying types can miss predict and cost more than type switch with few cases
  • Prefer static types at package boundaries when possible
  • any + assert in loops is a smell (also escapes/allocs)

Experiment

go run .
package main
import "fmt"
func main() {
    var i any = 3
    if _, ok := i.(string); ok {
        fmt.Println("string")
    } else {
        fmt.Println("not string")
    }
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("panic:", r)
        }
    }()
    _ = i.(string)
}

What to notice: comma-ok is quiet; bare assert panics.

Try next: Benchmark type switch vs chained asserts on a closed set of types.