skip to content

Your generic test helper fails with "cannot infer Out". How do you diagnose it and reshape the signature?

level: seniorimportance: should knowfreq 40%

answer

  1. the error names the parameter
  2. look for it in the result only
  3. make it reachable from an argument
  4. a prefix can be written, so order matters
  5. forty table rows repeating one type list

basics

~20 s

The error names the type parameter the compiler could not solve. Find it in the signature: if it appears only in the result, no argument determines it. The fix is a parameter that mentions it, not type arguments at every call.

solid answer

~50 s

First read the message literally — Go names the offending type parameter, so `cannot infer Out` means `Out` is unreachable from the arguments at that call. Locate `Out` in the declaration; nine times out of ten it appears only in the result type, or only inside a parameter the caller did not pass. Then choose the fix deliberately. Adding a parameter that carries the type is usually best: a table-test helper that returns `Case[In, Out]` should take the expected value, `want Out`, which both fixes inference and improves the API. Reordering the type parameters so the non-inferable one comes first lets callers write a prefix such as `NewCase[string]`. Telling every caller to write the full type argument list is the worst outcome — it spreads noise across the whole test suite and is what makes a generic helper unreadable to someone joining the team.

code

go · 10 lines
go
type Case[In, Out any] struct {
	Name  string
	Input In
	Want  Out
}

// Calls report: cannot infer Out
func NewCase[In, Out any](name string, input In) Case[In, Out] {
	return Case[In, Out]{Name: name, Input: input}
}

go deeper

for a junior

Know that the compiler tells you which type parameter it could not work out, and that the usual cause is a type that appears only in what the function returns.

for a middle

Be able to walk the signature and point to why the named parameter is unreachable, then show the two mechanical fixes: add a parameter of that type, or write the type arguments at the call.

for a senior

Show judgment about which fix to apply — reshaping the signature, splitting the helper, or accepting explicit instantiation — and argue it from what the call sites will look like for someone reading the package cold.

for a principal

Own the API consequence: type parameter order and inferability are part of an exported contract, so fixing them after a helper is depended on is a breaking change that has to be scheduled rather than slipped in.

## Reading the error Go's inference failures are precise, and the precision is the whole diagnostic. The message names the type parameter it could not solve: ``` ./cases_test.go:31:12: in call to NewCase, cannot infer Out (cases_test.go:18:19) ``` Two positions are given: the call, and the declaration of the type parameter. That is enough to skip guessing entirely. Open the declaration and ask one question about the named parameter: **can the compiler reach `Out` from the types of the arguments actually passed?** ## The three ways it becomes unreachable Consider a table-test harness. The natural first draft looks like this: ```go type Case[In, Out any] struct { Name string Input In Want Out } func NewCase[In, Out any](name string, input In) Case[In, Out] { return Case[In, Out]{Name: name, Input: input} } ``` `In` is fine — it is matched by the `input` argument. `Out` appears only in the result type, so nothing at the call site determines it, and every call fails. The three recurring shapes are: 1. **Result-only.** The type parameter appears solely in the return type. This is the common case, and it is what constructor-shaped helpers hit. 2. **Behind an unpassed value.** The parameter appears in an optional or variadic parameter that a particular call leaves empty, so *that* call cannot infer it even though others can. 3. **Only inside a constraint or a nested position the arguments never reach**, for instance a type parameter used only as the element of a type another parameter merely mentions in its constraint. ## Choosing a fix The options are not equivalent, and this is where seniority shows. **Add a parameter that carries the type.** Usually the best answer, and often better API anyway: ```go func NewCase[In, Out any](name string, input In, want Out) Case[In, Out] ``` A test case that does not state its expected value was under-specified regardless; inference just made the design flaw compile-visible. The rule of thumb: if the missing type parameter names a value the caller conceptually has, take it. **Reorder the type parameters.** A partial type argument list must be a prefix, so declaring the non-inferable one first lets callers write only that one: `NewCase[string](...)` instead of `NewCase[string, int](...)`. Cheap, but it is a breaking change once the helper is published, so make the ordering decision when you write the signature rather than after. **Split the helper.** Two focused functions often beat one doubly-generic one. If in practice `Out` is always `string` in one package and always `int` in another, a concrete helper in each is more readable than a generic one that nobody can call without decoration. **Push the type arguments onto callers.** Legitimate only when the type genuinely cannot be derived — a decoder that produces a value of a requested type, say. Then it is honest, and `Decode[Config](data)` reads well. It is the wrong answer when it is chosen simply because it makes the compiler stop complaining. **What not to do:** invent a dummy parameter purely to feed inference (`_ Out`), or widen `Out` to `any` and assert on the way out. The first is a puzzle for the next reader; the second throws away the type safety that motivated the generics in the first place. ## The onboarding argument This matters most for the person joining the team. A helper that infers reads as ordinary Go: ```go cases := []Case[string, int]{ NewCase("empty", "", 0), NewCase("digits", "42", 42), } ``` The same helper without inference forces `NewCase[string, int]("empty", "", 0)` on every row, and a table of forty rows carries forty copies of a type list that never changes. New readers cannot tell whether the brackets are load-bearing. Reviewing a generic helper, the question to ask out loud is not "does it compile" but "what does the call site look like from here to the end of the file". ## Confirming the diagnosis If the signature is long enough that the cause is not obvious, write the type arguments out explicitly at one call site. If the call then compiles, inference — not the constraint, not the argument types — was the problem, and the type parameter named in the original error is the one to make reachable. If it still fails, the message will now be about constraint satisfaction or assignability instead, which is a different bug.

  • When is pushing explicit type arguments onto callers the right answer?
    When the type genuinely originates with the caller rather than with any value they hold — a decoder or a zero-value factory, where `Decode[Config](data)` states the intent clearly. The test is whether the bracketed type adds information a reader wants. If it merely repeats what the arguments already say, the signature should be reshaped instead.
  • Why does reordering the type parameters help, and what does it cost?
    A partial type argument list must be a prefix, so putting the non-inferable parameter first lets callers write `Helper[string](...)` and infer the rest. The cost is that the order is part of the exported API: changing it later breaks every call site that supplies type arguments, so it is a decision to make when the signature is first written.
  • How do you confirm that inference, rather than the constraint, is the failure?
    Write the full type argument list at one call site. If it compiles, the type parameter named in the original message simply was not reachable from the arguments, and the fix is a signature change. If it still fails, the new error will be about constraint satisfaction or assignability, which points at the constraint or the argument types instead.
  • What is wrong with adding a blank parameter just to satisfy inference?
    A parameter like `_ Out` exists only to feed the compiler, so it carries no meaning at the call site and every caller must construct a value they do not use. The next reader has to work out why it is there. If no real value of that type belongs in the signature, it is more honest to have callers write the type argument.

saying these in an interview costs you the question

  • Ignores that the error names the exact type parameter
  • Fixes every failure by making callers write the full type argument list
  • Adds a blank parameter purely to make inference succeed
  • Widens the type parameter to any and type-asserts afterwards
  • Reorders type parameters on a published helper without noting it breaks callers