In Go's reflect package, how do you invoke a method chosen by its name at runtime?
answer
- find it, then invoke it
- the lookup takes a string, not an index
- the receiver is already attached
- arguments and results are both []reflect.Value
- an unknown name is not an error
basics
~10 sWrap the value with reflect.ValueOf, look the method up with Value.MethodByName, then invoke it with Value.Call, passing arguments as a []reflect.Value. Call hands back the results as []reflect.Value, which you unwrap with Value.Interface.
solid answer
~40 sThree steps. `reflect.ValueOf(target)` gives you a `reflect.Value`; `MethodByName("Drain")` looks the method up by string and returns a *bound method value* — the receiver is already attached, so it behaves like a plain func of the declared parameters. You then call it with `Call([]reflect.Value{...})`, wrapping each argument with `reflect.ValueOf`, and get back a `[]reflect.Value` holding the declared results, which you unwrap with `Value.Interface()` plus a type assertion. Two things bite immediately: an unknown name returns the *zero* `reflect.Value` rather than an error, so guard with `IsValid()` before calling, and only **exported** methods are visible to reflection. Nothing here is checked at compile time — a wrong argument count or type is a run-time panic out of `Call`.
code
go · 17 linestype Admin struct{ node string }
func (a *Admin) Drain(reason string) error {
fmt.Printf("draining %s: %s\n", a.node, reason)
return nil
}
// name arrives from an operator, as a plain string
func dispatch(target any, name, reason string) error {
m := reflect.ValueOf(target).MethodByName(name)
if !m.IsValid() {
return fmt.Errorf("unknown command %q", name)
}
out := m.Call([]reflect.Value{reflect.ValueOf(reason)})
err, _ := out[0].Interface().(error) // nil error yields a nil any
return err
}go deeper
Be ready to write the three lines from memory: reflect.ValueOf, MethodByName, Call with a []reflect.Value. Know that the receiver is already bound and that results come back as a slice.
Explain what MethodByName actually returns, why an unknown name is a zero Value rather than an error, and why only exported methods are visible. Say how you unwrap an error result.
Show the guards you would put around this in production: IsValid before Call, signature validation before arguments are built, and a recover boundary so an operator typo cannot kill the process.
Own the question of whether name-addressable dispatch belongs in the codebase at all, and what a team gives up — compile-time references, rename safety, a bounded surface — when it resolves methods from strings.
## The problem reflection solves here Ordinary Go dispatch is decided when you write the code: you name a method, the compiler resolves it, the linker wires it up. Sometimes the name only exists at run time — an operator types `drain` into an admin console, and a command bus has to find the handler that matches. `reflect` is the standard library's answer: it lets a program look at a value's type and methods as data, and invoke one chosen by a string. ## The three steps **1. Get a `reflect.Value`.** `reflect.ValueOf(x)` takes any value and returns a `reflect.Value` — a run-time handle on the value together with its dynamic type. **2. Look the method up.** `Value.MethodByName(name string) Value` searches the value's method set for a method with that name and returns a *method value*: a `reflect.Value` of func kind whose receiver is already bound. That binding matters — the func type it reports through `Type()` lists only the declared parameters, so you do **not** pass the receiver yourself. (The other route, `Type.Method(i)`, gives you a `Func` whose first parameter *is* the receiver; the two are easy to confuse.) If no such method exists, `MethodByName` does not return an error and does not panic. It returns the **zero** `reflect.Value`, for which `IsValid()` reports false. Calling `Call` on that zero value panics with `call of reflect.Value.Call on zero Value`, so a name arriving from a user is always checked first. Only **exported** methods are reachable. For a non-interface type, `reflect` deliberately reports only the exported method set, so `MethodByName("drain")` on a method named `drain` finds nothing — reflection does not breach package encapsulation. **3. Call it.** `Value.Call(in []Value) []Value` invokes the func. Each element of `in` must be *assignable* to the corresponding declared parameter type; the results come back as a slice in declaration order. To build an argument you wrap it: `reflect.ValueOf("node-7")`. To read a result you unwrap it: `out[0].Interface()` returns an `any`, which you type-assert. For a variadic method, `Call` collects the trailing arguments into the variadic slice for you; `CallSlice` is the variant that takes an already-built slice as the final argument. ## Getting an error result out A handler that returns `error` needs one extra piece of care. `out[1].Interface()` on a nil error yields a nil `any`, and a single-value type assertion on a nil interface **panics**. Use the comma-ok form: err, _ := out[1].Interface().(error) When the call succeeded, `err` is nil and `ok` is false, which is exactly what you want. `out[1].IsNil()` works too, because the result's kind is `Interface`. ## What is not checked Nothing about this call is verified by the compiler. The method name is a string, the arguments are a slice, and every mismatch — wrong count, wrong type, unexported name, nil receiver — shows up as a run-time panic or a silent miss. That is the trade you are making: you buy dispatch from data, and you pay with checks the compiler used to do for you. A dispatcher that takes operator input therefore validates the resolved method's signature before calling, and a panic escaping `Call` should not be allowed to take the process down. ## Which methods are visible `reflect.ValueOf(v)` exposes the method set of the value you handed it. Hand it a `T` and you see the methods declared on `T`; hand it a `*T` and you see those declared on `T` and on `*T`. A command bus therefore registers pointers to its handler structs, not copies of them. ## Cost A reflected call is far slower than a direct one — the lookup walks a name table, the arguments are boxed into `reflect.Value`s, and the call goes through the runtime rather than a direct branch. For an admin command that runs once a minute that is irrelevant; on a hot path it is not, and a plain `map[string]func(...)` registry is both faster and safer.
- What does MethodByName give you back when the name matches nothing, and what happens if you call it anyway?It returns the zero `reflect.Value` — no error, no panic at lookup time. `IsValid()` on it reports false. Calling `Call` on that zero value panics with `call of reflect.Value.Call on zero Value`, so a dispatcher that takes a name from a user must check `IsValid()` first and turn a miss into an ordinary `unknown command` error.
- The method returns (string, error). How do you pull the error out of the []reflect.Value results safely?Use the comma-ok assertion: `err, _ := out[1].Interface().(error)`. A nil error result unwraps to a nil `any`, and the single-value form `out[1].Interface().(error)` panics on that. `out[1].IsNil()` is an equivalent guard, since the result's kind is `Interface`.
- Why can't MethodByName reach an unexported method?For non-interface types `reflect` reports only the exported method set — `NumMethod` counts exported methods and `MethodByName` searches only those. Reflection is not a way around package encapsulation; an unexported helper stays unreachable from a name string, which is one of the few things that bounds a dynamic dispatch surface.
saying these in an interview costs you the question
- Thinks Call takes plain values rather than []reflect.Value
- Passes the receiver as Call's first argument
- Expects MethodByName to return an error for an unknown name
- Believes reflection can invoke unexported methods by name
- Type-asserts a nil error result without the comma-ok form
- Assumes the compiler checks the argument types of a reflected call