skip to content

What is `any` in Go, and what can you do with a value whose static type is `any`?

level: middleimportance: should knowfreq 58%

answer

  1. it is spelled with an equals sign
  2. an alias, not a new type
  3. empty method set, satisfied by everything
  4. arrived with generics in 1.18
  5. you must assert before you can use it

basics

~20 s

any is a predeclared alias for interface{} (Go 1.18), so both spellings are one identical type. It declares no methods, so every type satisfies it, and an any value must be type-asserted before you can use it.

solid answer

~50 s

`any` is declared in the universe block as `type any = interface{}` — an **alias**, not a new defined type. That matters: identical types, so `func(any)` and `func(interface{})` are assignable to each other, a `map[string]any` and a `map[string]interface{}` are the same map type, and mixing the two spellings in one codebase never needs a conversion. The empty interface has an empty method set, so every type implements it, including other interface types. The cost is that you can do almost nothing with the value directly: no methods, no arithmetic, no indexing. You can store it, compare it (which panics at run time if the dynamic type is not comparable), print it with `fmt`, and otherwise you must recover a usable type with a type assertion, a type switch, or `reflect`. `any` was added mainly so the generics syntax `[T any]` reads well.

code

go · 7 lines
go
// builtin: type any = interface{}

type Store map[string]interface{}

func Put(s Store, k string, v any) { s[k] = v }

var _ func(Store, string, interface{}) = Put // identical types

go deeper

for a junior

Be ready to say that any means the empty interface, that every type satisfies it, and that you cannot call methods on such a value without a type assertion or a type switch first.

for a middle

Explain that it is an alias and what identity buys you: interchangeable signatures, a mechanical rewrite, no conversions. Be able to list what is legal on the value itself, including the comparison hazard.

for a senior

Demonstrate the judgment about where any belongs. Expect to argue for converting untyped data into concrete types at the boundary rather than letting any leak through layers that each re-assert it.

for a principal

Own the API-shape call: a parameter typed any is checking deferred to run time and pushed onto every caller. Decide when a narrow interface or a type parameter should replace it, and what that costs to migrate.

## The declaration Go's `builtin` documentation package spells it out: ```go type any = interface{} ``` The `=` is the whole story. This is a **type alias**, introduced in Go 1.18 alongside generics, not a defined type. An alias creates a second spelling for one type, so `any` and `interface{}` are *identical* — not merely assignable, not convertible, identical. Consequences you can be asked to justify: - A `func(x any)` value may be assigned to a variable of type `func(interface{})` with no conversion. - `map[string]any` and `map[string]interface{}` are one type; a function declared with one can be handed the other. - Migrating a codebase from `interface{}` to `any` is a purely cosmetic, always-safe rewrite (`gofmt -r 'interface{} -> any'` does it mechanically), and half-migrated code still compiles. - If it had been a defined type (`type any interface{}`), it would have been a *different* type with the same method set: assignable in many places, but not identical, and generic code would have been noisier. ## Why every type satisfies it Go interfaces are satisfied structurally: a type implements an interface when its method set contains all the interface's methods. The empty interface requires **zero** methods, so the condition is vacuously true for every type — `int`, `struct{}`, a func value, a channel, and other interface values too. That is why `any` is the type of "a value of unknown type": a parameter that accepts anything, the element type of a heterogeneous container, the target of `json.Unmarshal` when you do not know the payload shape. ## What you can actually do with it Almost nothing directly, and that is the point. - **Store and pass it.** Assignment, slice and map elements, struct fields, channel sends. - **Compare it with == and !=.** This compiles, but it is a run-time hazard: comparing two interface values whose dynamic type is not comparable — a slice, a map, or a func — panics with `comparing uncomparable type`. Two interface values are equal when both dynamic types and both dynamic values are equal. - **Print it.** `fmt` takes `...any` and uses reflection; `%v` prints the value, `%T` the dynamic type. - **Recover a usable type.** A type assertion `v, ok := x.(int)`, a type switch, or the `reflect` package. Everything else — calling a method, adding one, indexing — requires that step first. ```go var x any = 3 // x + 1 // does not compile n, ok := x.(int) // n == 3, ok == true _ = n + 1 // fine, n is an int ``` ## any as a type-parameter constraint In `func Map[T any](...)`, `any` is used as a constraint and means "no constraint": the type set is all types. It reads better than `interface{}` in that position, which is the concrete reason the alias was added when generics landed. In constraint position it is the weakest constraint there is — you can pass values of `T` around but not compare them (that needs `comparable`) or order them. ## The design cost Every `any` in a signature is type checking moved from compile time to run time, and pushed onto the caller. The standard library uses it where the alternative does not exist — `fmt` printing arbitrary values, `encoding/json` decoding an unknown shape, `context.WithValue` carrying an opaque payload, `sync.Map` predating generics. In your own code, a parameter typed `any` usually means either a missing interface (name the one or two methods you actually call) or a missing type parameter (generics give you the same flexibility with compile-time checking and no boxing). A good rule for a review: `any` is acceptable at a boundary where data genuinely arrives untyped, and should be converted into concrete types as early as possible after that. `any` flowing several layers into business logic means every one of those layers is asserting, and one of them will use the panicking form. ## Nesting does not happen One subtlety worth knowing: assigning an interface value to an `any` does not wrap one interface inside another. The dynamic type and value are copied across, so an `error` stored in an `any` still asserts back to its original concrete type — `x.(*os.PathError)` works directly. There is only ever one level of dynamic type.

  • Does every type satisfy any, including other interface types?
    Yes. Satisfaction requires containing the interface's methods, and the empty interface has none, so the condition holds for every type — concrete types and interfaces alike. Assigning an `error` to an `any` does not nest interfaces: the dynamic type and value are carried across unchanged, so you can assert straight back to the original concrete type.
  • Is comparing two values of type any with == always safe?
    It compiles, but it can panic. Interface comparison compares dynamic types first, then dynamic values, and if the dynamic type is not comparable — a slice, a map, or a func — the runtime panics with `comparing uncomparable type`. Comparing values you did not construct yourself is therefore a hazard; guard with a type switch, or use `reflect.DeepEqual` when structural equality is what you meant.
  • Why was any introduced as an alias rather than a new defined type?
    So that both spellings denote one type. A defined type would have been distinct: signatures using it would not have been identical to existing `interface{}` signatures, and every mixed codebase would have needed conversions or a flag day. As an alias, the rewrite is cosmetic and a half-migrated package still compiles.

saying these in an interview costs you the question

  • Calls any a distinct type from interface{}
  • Thinks any is a supertype in an inheritance hierarchy
  • Believes any values carry no type information at run time
  • Assumes == on two any values can never panic
  • Says storing an error in an any nests interfaces