In a generic function `Find[T any](rows []T, match func(T) bool) (T, bool)`, how do you produce the T you return when nothing matches?
answer
- T could be int, string or a struct
- no literal is valid for every T
- declare it instead of writing it
- one line: var zero T
- the bool tells the caller it is meaningless
basics
~20 sDeclare a variable of the type parameter and return it: var zero T, then return zero, false. That is the zero value of whatever type T was instantiated with. No literal, not nil, 0 or T{}, is valid for every T.
solid answer
~50 sYou cannot write a literal, because you do not know what `T` is: `nil` is invalid unless `T` happens to be a pointer, map, slice, channel, func or interface type; `0` only works for numbers; `T{}` only compiles if `T` is a struct, array, slice or map type. The one form that works for every type argument is to declare a variable of the type parameter and return that: `var zero T; return zero, false`. Go zeroes every declared variable, so `zero` is `0`, `""`, `nil`, `false` or an all-zero struct depending on what the caller instantiated. `*new(T)` is the same value written the older way; `var zero T` is clearer and the compiler does not heap-allocate it. Pair it with the comma-ok `bool` so the caller never has to guess whether a legitimate zero was found.
code
go · 9 linesfunc Find[T any](rows []T, match func(T) bool) (T, bool) {
for _, r := range rows {
if match(r) {
return r, true
}
}
var zero T // 0, "", nil or an all-zero struct, depending on T
return zero, false
}go deeper
Memorise the one-liner: var zero T, then return zero, false. Be ready to say why nil and 0 do not compile when the type parameter is constrained only by any.
Explain that a type parameter is a compile-time placeholder and that Go guarantees a zero value for every type, so declaration is the only universal way to name it. Mention *new(T) as the equivalent older form.
Show that you design the signature, not just the body: the comma-ok bool exists because the zero value is a legitimate result, and a helper that made callers compare against zero would push a bug into every call site.
Frame it as an API convention question. A shared package's generic lookups should all use the same shape as the standard library's comma-ok pairs, so callers never have to remember which helper signals absence with a pointer and which with a bool.
## Why a literal does not work Inside a generic function, `T` is a *type parameter*: at compile time the function is checked against everything the constraint permits, and with `any` that is every type in the language. So any value you write must be valid for `int`, `string`, `time.Duration`, `[]byte`, `map[string]int`, `*os.File`, a struct and an interface, all at once. No literal satisfies that: - `return nil, false` fails to compile unless the constraint's type set contains only nil-able types (pointer, slice, map, channel, func, interface). With `T any` it does not. - `return 0, false` fails for anything that is not numeric. - `return "", false` fails for anything that is not a string. - `return T{}, false` is a composite literal, which is only legal when `T`'s core type is a struct, array, slice or map. For `T = int` it is a compile error. ## The idiom ```go func Find[T any](rows []T, match func(T) bool) (T, bool) { for _, r := range rows { if match(r) { return r, true } } var zero T return zero, false } ``` The Go specification says that a variable declared without an initialiser gets its type's *zero value*: `0` for numeric types, `false` for booleans, `""` for strings, `nil` for pointers, slices, maps, channels, funcs and interfaces, and a struct or array whose every field or element is itself zeroed. `var zero T` is therefore the one spelling that is legal and correct for every possible instantiation, and the compiler emits exactly the zeroing the concrete type needs. Generics in Go are not erased. When the function is instantiated with `T = string`, the code really does return an empty string; nothing is boxed into an interface and nothing is decided at run time by reflection. ## `*new(T)` and the newer alternatives `*new(T)` allocates a `T`, zeroes it and dereferences it, producing the same value. You will see it in older generic code and in code that wants the zero value as an expression rather than as a statement — for example inside a composite literal or a `return` in the middle of an expression. It compiles to the same thing in practice, because the pointer does not escape and the compiler removes the allocation. `var zero T` reads better and is what the standard library uses. (Go 1.26 extended `new` so it can take an expression, as in `new(42)` for a `*int`. That is about initialising through a pointer, not about naming a zero value, and it does not replace this idiom.) ## Why the second return value matters The zero value is a real, legitimate value. If a coverage-report aggregator searches `[]int` for a package's line count, `0` is a perfectly plausible answer *and* the not-found result. That ambiguity is exactly why the comma-ok shape exists: ```go lines, ok := Find(counts, func(n int) bool { return n > threshold }) if !ok { // no row matched; lines is meaningless } ``` The caller must branch on `ok`, not on `lines == 0`. Comparing against the zero value has a second problem in generic code: `==` is only available when the type parameter is constrained to comparable types, so a helper that tried to do the comparison for you would not compile under `T any` at all. An alternative API returns `*T`, using `nil` for "not found". That removes the ambiguity too, but it forces a nil check on every caller, gives the caller a pointer into (or a copy of) your data, and usually pushes the value onto the heap. Go's own convention — map lookups, type assertions, `slices.BinarySearch` — is the `(value, bool)` pair, so a generic helper should match it. ## What interviewers are listening for The wrong answers all come from assuming `T` is more specific than it is: "just return nil" (assumes reference types), "return `T{}`" (assumes structs), "return `-1` as a sentinel" (assumes numbers, and re-invents a bug). The right answer is one line, and it shows you understand that a type parameter is a compile-time placeholder for *any* type, and that Go guarantees a zero value for all of them.
- Can the caller just compare the returned value against the zero value instead of checking the bool?No, for two reasons. A genuine result may itself be the zero value — `0` line counts or an empty string are real answers — so the comparison cannot distinguish found from not-found. And `==` is not available inside a helper constrained by `any`; it needs a comparable constraint. The `bool` is the authoritative signal.
- Is `*new(T)` equivalent to `var zero T`?Yes, it yields the same zero value, and it is the older spelling you still see in generic code because it is an expression rather than a statement. The compiler does not actually keep the allocation, since the pointer never escapes. `var zero T` is clearer and is what the standard library uses.
- When would you return `*T` instead of `(T, bool)`?When the caller needs to mutate the found element in place, or when `T` is large and you want to avoid copying it. The cost is a nil check at every call site and a value that usually escapes to the heap. Go's own APIs favour the comma-ok pair, so deviate only with a reason.
It is like a form that must work for every kind of applicant: you cannot pre-print an answer, so you leave the box blank and tick a separate 'no data' checkbox alongside it.
saying these in an interview costs you the question
- Says return nil works for any T
- Believes generics are erased so T defaults to nil
- Writes T{} assuming every type parameter is a struct
- Uses a sentinel like -1 instead of the bool
- Tells the caller to compare the result against zero
- Thinks var zero T allocates on the heap