In a Go type switch `switch v := x.(type)`, what type does v have in each case?
answer
- one type per case, or not
- the binding is per-clause
- multi-type case gives you nothing concrete
- first matching clause wins, top down
- case nil tests the interface itself
basics
~20 sA case naming exactly one type gives v that type. In a case listing several types, and in default, v keeps the static type of x, which is the interface type. Cases are tried in source order.
solid answer
~50 s`switch v := x.(type)` evaluates the dynamic type of the interface value `x` once and binds a fresh `v` in each clause. In a clause naming exactly one type, `v` has that type, so you can use it directly — `case int:` gives you an `int`. In a clause listing several types, and in `default`, there is no single type to give it, so `v` keeps `x`'s interface type and you would have to assert again to use it concretely. `case nil:` matches when the interface value itself is nil. Clauses are tested in source order and the first match wins, which matters when a case names an interface type, since one value can satisfy several. Listing the same type twice is a compile error. A type switch is the readable replacement for a chain of comma-ok assertions.
code
go · 14 linesfunc kind(x any) string {
switch v := x.(type) {
case nil:
return "nil interface value"
case int:
return fmt.Sprintf("int, doubled = %d", v*2) // v is an int
case string, []byte:
return fmt.Sprintf("text: %v", v) // v is still any here
case error:
return "an error: " + v.Error() // v is an error
default:
return fmt.Sprintf("something else: %T", v) // v is still any
}
}go deeper
Be ready to write the syntax correctly, including the .(type) guard and a default clause, and to say that a single-type case lets you use the value directly without another assertion.
Explain the binding rule precisely — one type versus several, and what default gives you — plus source-order matching and what case nil tests. This is the layer the question is aimed at.
Show when a type switch is the wrong answer: dispatching over your own implementations should be a method call. Expect to justify a default clause that reports unexpected types instead of dropping them.
Own where dispatch on dynamic type is allowed to live at all. A type switch at a decoding boundary is fine; one spreading through domain code is a signal the type model, not the switch, needs fixing.
## The form ```go switch v := x.(type) { case nil: // x is a nil interface value case int: // v is an int case string, []byte: // v still has x's interface type default: // v still has x's interface type } ``` The guard `v := x.(type)` is legal **only** in the head of a switch — it is not an expression you can write anywhere else. `x` must be of interface type, exactly as for a normal assertion. ## What v is bound to This is the part interviewers actually probe. The specification says: in a clause listing **exactly one type**, the variable has that type; **otherwise** it has the type of the guard's expression. So: - `case int:` — `v` is an `int`. You can do arithmetic on it with no further assertion. - `case error:` — the single type is an interface, so `v` is an `error` and you may call `v.Error()`. - `case string, []byte:` — two types, so `v` is still whatever `x` was declared as (`any`, `io.Reader`, …). Calling `len(v)` there does not compile even though both listed types support it; there is no union type in Go. - `default:` — same, `v` keeps the interface type. A useful consequence: in the multi-type and default clauses, `%T` on `v` still prints the dynamic type, which is why `fmt.Sprintf("unhandled: %T", v)` is the standard default-clause line. The binding is per-clause: each clause gets its own `v`, which is why the compiler can give it a different type in each one. You may omit the binding entirely — `switch x.(type) { case int: ... }` — when you only care which branch runs. ## Order matters when cases name interfaces Cases are evaluated top to bottom and the first match wins. With concrete types that is invisible, because a value has exactly one dynamic type and at most one case can name it (naming a type twice is a compile-time duplicate-case error). With **interface** cases it is decisive: a `*os.PathError` matches both `case error:` and `case *os.PathError:`, and whichever appears first is the one that runs. Put the narrow, specific cases above the broad interface cases, or the broad one will swallow everything. This is different from a language with runtime overload resolution: Go does not look for the most specific match, it takes the first textual one. ## case nil `case nil:` matches when the interface value itself is nil — no dynamic type at all. It is the only way to distinguish "nothing was passed" from "a value was passed" inside the switch, and without it a nil interface falls through to `default`. Note that this is a test on the interface value itself, not on whether the concrete value inside it happens to be a nil pointer or a nil map. ## Why it exists Without a type switch you would write a ladder: ```go if i, ok := x.(int); ok { ... } else if s, ok := x.(string); ok { ... } ``` The switch is shorter, evaluates the guard expression once, keeps every branch's variable correctly typed, and makes the exhaustiveness of your handling visible in one screen. It is what the standard library uses when it must dispatch on a decoded, unknown-shape value. ## Where a type switch is the wrong tool If every case does the same thing through the same method, you wanted an interface, not a switch: define the method and let dispatch do the work. If the switch enumerates the concrete implementations of your own interface, it will need editing every time a new one appears, which is exactly the coupling interfaces exist to remove. A type switch earns its place when the values genuinely come from outside your type system — decoded JSON, a plugin registry, a value of type `any` handed to a formatting routine — or when you must handle a fixed, closed set of types that you own together. One last practical note: keep a `default` clause that reports the unexpected type rather than silently doing nothing. A type switch with no `default` over data you do not control is a silent-drop bug waiting for the first payload shape you did not anticipate.
- Can a case in a type switch name an interface type, and what does that match?Yes. `case error:` or `case fmt.Stringer:` matches whenever the dynamic type's method set contains that interface's methods. Because a single value can satisfy several interfaces, and can also match a concrete case, source order decides which clause runs — the first match wins. Put specific concrete cases above broad interface cases, otherwise the broad one absorbs them.
- What happens if the same type appears in two cases of one type switch?It is a compile-time error: duplicate case in the type switch. Unlike interface cases, which can legitimately overlap, naming a concrete type twice is unambiguously a mistake, so the compiler catches it rather than silently making the second clause unreachable.
- Do you always need the `v :=` binding?No. `switch x.(type) { ... }` is legal when the branches only need to know which case matched and never touch the value. Dropping the binding also avoids the unused-variable question in clauses that ignore it, and it documents that the branch cares about the shape of the input rather than its contents.
saying these in an interview costs you the question
- Thinks v has the specific type inside a multi-type case
- Expects the most specific case to win regardless of order
- Believes cases can bind a union of the listed types
- Uses reflection where a type switch would do
- Writes a type switch over their own interface's implementations