Given func Min[T cmp.Ordered](a, b T) T, what does Go infer for Min(1, 2), and why can that surprise you?
answer
- constants have a default type
- typed arguments win over constants
- 1 defaults to int, 1.0 to float64
- the result is typed, not untyped
- the constraint checks, it does not choose
basics
~20 sT is inferred as int, so the call returns an int. Untyped constants contribute their default type to inference, and int is the default for 1 and 2 — the constraint permitting float64 does not make the compiler choose it.
solid answer
~40 sInference considers typed arguments first: if any argument has a type, that type solves the type parameter and untyped constants in the same call simply convert to it. Only when every relevant argument is an untyped constant does the constant's default type decide — `int` for `1`, `float64` for `1.0`, `rune` for `'a'`, `string` for a string literal. So `x := Min(1, 2)` gives `T = int` and an `int` result, and `var f float64 = Min(1, 2)` does not compile, because an `int` result is not assignable to a `float64` variable. If you have a typed argument, `Min(1, f)` with `f float64` infers `float64`. To force it otherwise, instantiate explicitly with `Min[float64](1, 2)` — converting the result afterwards is too late, since the comparison already happened in `int`.
code
go · 14 linesfunc Min[T cmp.Ordered](a, b T) T {
if a < b {
return a
}
return b
}
func demo() {
var limit float64 = 2.5
x := Min(1, 2) // T = int (default type of the constants)
y := Min(1, limit) // T = float64 (from the typed argument)
z := Min[float64](1, 2) // T written explicitly
_, _, _ = x, y, z
}go deeper
Know that a bare 1 in Go is an int by default, and that passing only such constants to a generic helper gives you an int result even when the function could have worked with floats.
Be ready to state the ordering: typed arguments determine the type parameter, and untyped constants fall back to their default type only when nothing typed is present.
Show you can read the downstream error — a mismatch on a later assignment rather than a cannot-infer message — and that you reach for explicit instantiation rather than a cosmetic conversion.
Treat it as documentation debt on shared numeric helpers: if the intended type argument is not what the defaults produce, the package should say so, or take a typed parameter that removes the ambiguity.
## Untyped constants, briefly In Go, `1`, `2.5`, `'a'` and `"hi"` are *untyped constants*. They have a default type (`int`, `float64`, `rune`, `string`) that is used when a constant must be given a concrete type, but in ordinary contexts they adapt: `var f float64 = 1` is fine, and so is `var b byte = 1`, because the constant takes on the type the context demands. Generic inference has to reconcile that flexibility with the need to pick exactly one type argument. ## The two-phase rule The compiler resolves a call's type parameters in stages, and the part worth remembering is the priority: 1. **Typed arguments first.** Any argument that already has a type contributes an equation. Given `func Sum[T int | float64](vals ...T) T` and `var f float64`, the call `Sum(f, 1, 2)` solves `T = float64` from `f`; the untyped constants `1` and `2` then convert to `float64` without complaint. 2. **Untyped constants only if needed.** If no typed argument determines the type parameter, the untyped constants' default types are used. `Min(1, 2)` therefore gives `T = int`. The practical summary: a constant never *overrides* a real type, but in the absence of one it falls back to its default, which is very often `int` when the author wanted a float. ## The surprise ```go x := Min(1, 2) // x is int var f float64 = Min(1, 2) // does not compile: int is not float64 ``` The second line looks like it should work — after all, `var f float64 = 1` works. The difference is that `Min(1, 2)` is not an untyped constant expression; it is a *call*, and a call's result is a typed value. Once `T` is solved as `int`, the result type is `int`, and assignment rules apply as they do to any `int`. The constant's flexibility was consumed at the call site. The same shape bites with sized integers: ```go var limit int64 = 100 _ = Min(limit, 50) // fine: T = int64 from the typed argument _ = Min(50, 100) // T = int, whatever you meant ``` ## Fixing it There are three honest fixes, and the choice matters: - **Instantiate explicitly:** `Min[float64](1, 2)`. Unambiguous, and correct when the caller genuinely wants a wider type. - **Make one argument typed:** `Min(1.0, 2.0)` gives `float64` because the default type of a floating-point constant is `float64`; passing an existing typed variable does the same. - **Do not** convert afterwards. `float64(Min(1, 2))` compiles, but the comparison already ran in `int`, so any truncation or overflow in getting the arguments there has already happened. It changes the result's type, not the computation. ## Why this is worth knowing Generic numeric helpers are one of the first things teams write after adopting type parameters, and this is where they first hit a confusing error message. The error is usually not `cannot infer T` — inference *succeeded*, just not with the type the author had in mind — so it appears further along as a type mismatch on an assignment or on a later call, sometimes several lines away. Recognising "the constants defaulted to `int`" turns a puzzling error into a one-token fix. It also explains a piece of API advice: for a numeric helper meant to be used with floats, taking a typed parameter or documenting the explicit instantiation is kinder than letting every caller discover the default. ## A note on the constraint Nothing about the constraint changes the answer. `cmp.Ordered` admits every ordered type including `float64`, but a constraint says which type arguments are *allowed*, never which one is *chosen*. Candidates who expect the compiler to pick a type from the constraint's type set have the relationship backwards: inference proposes a type from the call, and the constraint then checks it.
- Why does converting the result, as in float64(Min(1, 2)), not solve the problem?Because the conversion happens after the call has already been instantiated with `T = int` and the comparison has already run in `int`. You get a `float64` value, but it is a `float64` copy of an `int` result. If either argument could not be represented as an `int` in the first place, the code would not have compiled, or would have truncated before the call. Instantiate explicitly instead.
- Does the constraint's type set influence which type is inferred?No. A constraint is a filter, not a chooser: inference proposes a type argument from the call site, and the constraint is then checked against it. A `cmp.Ordered` constraint permits `float64`, but it never causes `float64` to be selected. If the proposed type fails the constraint, you get a constraint violation error rather than a different inference.
saying these in an interview costs you the question
- Says the compiler picks a type out of the constraint's type set
- Expects the assignment target to widen the inferred type
- Thinks the call's result stays untyped and adapts like a constant
- Fixes the mismatch with a conversion instead of explicit instantiation
- Assumes an untyped constant beats a typed argument in the same call