Go Module 11
Generics, including generic methods (1.27) interview questions
Interview questions on generics, including generic methods (1.27): warm-ups, then what each harder question tests and how a good answer starts.
Warm-up questions
Short questions with short answers, each answered in full.
-
Fresher
What is the difference between a type parameter and a type argument in Go?
Answered in the lesson Type parameters: generic functions and inference
-
Fresher
What does the ~ in a Go constraint such as ~int mean?
Answered in the lesson Constraints, type sets and cmp.Ordered
-
Fresher
How do you return "nothing" from a method of a generic type such as Stack[T]?
Answered in the lesson Generic types, data structures and type aliases
-
Fresher
What did Go 1.27 change about methods and type parameters?
-
Mid
What is a self-referential constraint in Go?
Answered in the lesson Advanced generics: inference and self-reference
-
Mid
How does the Go compiler implement generics?
Answered in the lesson Generics in practice: performance and API design
Questions with model answers
What each question tests, the first step of a good answer and the follow-ups to expect.
-
Mid to senior
A pull request changes func Send(n Notifier, msg string) error into func Send[N Notifier](n N, msg string) error, and its description says that type parameters make the call faster. Review the change: what do you ask for, and what do you suggest?
- What it tests
- Knowing when a type parameter adds nothing, how the gc compiler builds generic code, and asking for a benchmark before accepting a claim about speed.
- First step
- Point out that Send only calls a method of n, the case the Go blog gives to interfaces, and that the generic version still calls Notify indirectly. Ask for a b.Loop benchmark of both versions with the real notifier types; until it shows a gain that matters, keep the interface, which is simpler to read and to fake.
- Follow-ups
-
- Can the generic version be slower than the interface one?
- When would a type parameter on Send be justified?
- How would you measure the claim fairly?
From the lesson Type parameters: generic functions and inference
Model answer, Pro. Model answers open with any pass; prices are in the notice on this page.Opening the model answers with your pass…
Model answers open with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so the model answers can’t be opened at the moment. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
A generic Dedupe[T comparable](xs []T) []T has passed its tests for a year. After a change feeds it values decoded from JSON into a []any, production logs show runtime error: hash of unhashable type []interface {}. Why did it compile, why does it panic, and how do you fix it?
- What it tests
- The type set of comparable, why interface types satisfy it since Go 1.20, which values make == and map keys panic, and fixes that move the check to compile time or to a key function.
- First step
- Explain that any satisfies comparable since Go 1.20, because == is defined on interface values, so the call compiled. The panic comes when a value inside holds a slice, which a nested JSON array decodes to, and Dedupe uses it as a map key. Then fix the design: dedupe on a comparable key, or decode into a concrete type.
- Follow-ups
-
- Why did tests on strings and numbers never catch it?
- Would recovering from the panic inside Dedupe be a fix?
- How do you build a comparable key from a nested JSON value?
From the lesson Type parameters: generic functions and inference
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
RoundToRupee[T Number](p T) T, with Number being ~int | ~int64 | ~float64, fails with operator % not defined on p. A teammate proposes a type switch on any(p) inside the function, with math.Mod for the float case. What do you recommend instead, and why?
- What it tests
- Reading the error as a type-set problem, keeping constraints as small as the operations need, and knowing why a type switch on a type parameter undoes what the constraint was for.
- First step
- Explain the error: % must work for every type in Number's set, and float64 is in it. Then ask what the function is for. Money in paise is an integer, so narrow the constraint to ~int | ~int64 and keep % working; if floats really need rounding, give them their own function rather than a type switch inside a generic one.
- Follow-ups
-
- Why is a type switch on any(p) a poor fit here?
- What happens to callers that pass a named type declared on int64?
- How would you round floating-point amounts, if the product needs them?
From the lesson Constraints, type sets and cmp.Ordered
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
A generic Max[T cmp.Ordered](xs []T) T over sensor readings returns different maxima for the same readings in a different order, and only in production, where some readings are NaN. Diagnose it, fix it, and say how a test would have caught it.
- What it tests
- Knowing that comparisons with NaN are false, why a > loop then depends on order, the documented NaN rules of cmp.Compare and slices.Max, and tests that put NaN in more than one position.
- First step
- Reproduce it first: run the same readings in two orders, one of them starting with NaN, and compare the results. Every comparison with NaN is false, so trace what a loop written with > does when the NaN is its first value and when it comes later. Then decide what a NaN should mean in this data before choosing how the comparison treats it.
- Follow-ups
-
- Which of the two rules would you choose for sensor data, and why?
- Does the same problem affect sorting?
- Could the constraint keep floats out instead?
From the lesson Constraints, type sets and cmp.Ordered
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 2 minutes
-
Fresher to mid
A generic Cache[K comparable, V any] has Get(k K) V, which returns the zero value on a miss. A service caches computed discounts, many of them 0, and its profile shows the discount recomputed on almost every request. Explain the cause and redesign the API.
- What it tests
- Seeing that the zero value of V cannot tell a miss from a stored zero, the (V, bool) convention, and how to change an exported method without breaking its callers.
- First step
- Show that Get returns 0 both for a miss and for a cached discount of 0, so the caller treats every zero as a miss and recomputes it. Then change the signature to Get(k K) (V, bool), as a map's comma-ok read and the lesson's cache do, and make callers branch on the bool.
- Follow-ups
-
- How do you change Get without breaking every caller at once?
- Would returning a *V instead be better?
- How do you test that a cached zero is served from the cache?
From the lesson Generic types, data structures and type aliases
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 2 minutes
-
Mid to senior
Your team is moving a generic type Ledger[T] with 200 call sites from package billing to a new package, over several weeks. A colleague's first change declares type Ledger[T any] accounts.Ledger[T] in billing. Review it and plan the move.
- What it tests
- Knowing the difference between a generic alias and a generic definition, why identity and methods matter in a move spread over many changes, and the order of a safe migration.
- First step
- Point out the missing =: without it the line defines a new type rather than a second name for accounts.Ledger, so old and new code would hold two different types during the move. Ask for type Ledger[T any] = accounts.Ledger[T], a generic alias, with a Deprecated: note, so both names stay one type while callers move.
- Follow-ups
-
- Which Go version does the alias need, and what if a module's go line is older?
- In what order do you move the code, and when may the alias go?
- Can billing keep a method that only billing needs?
From the lesson Generic types, data structures and type aliases
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
An LRU cache stores entries in container/list and reads e.Value.(*entry) on every Get. A colleague proposes a generic LRU[K comparable, V any] with its own linked list. What do you gain, what must the new version keep, and how do you show that it is correct?
- What it tests
- Typed nodes instead of type assertions, the map and doubly linked list that make Get and Put O(1), a constructor for a type whose zero value cannot work, and tests of eviction order.
- First step
- Name the gain: typed nodes remove the assertion on every read and turn a wrong key or value type into a compile error. Then list what must not change: Get, Put and eviction in constant time, and the order in which entries are evicted, which tests have to pin before and after the change.
- Follow-ups
-
- Is the generic version faster as well as safer?
- How would you make it safe for several goroutines?
- Which tests prove the eviction order?
From the lesson Generic types, data structures and type aliases
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 2 minutes
-
Mid to senior
A team replaces Config's Get(key string) (string, error) with a generic Get[T any](key string) (T, error). Code that passes a Config as a Source, an interface listing Get(key string) (string, error), stops compiling. Explain why, and design an API that keeps both.
- What it tests
- Knowing that a generic method never implements an interface method, why the language works that way, and the ways to keep a non-generic method at the interface while offering typed reads.
- First step
- Explain the error: Config now has only the generic Get, and an interface method can be matched only by a non-generic one, so Config no longer satisfies Source even though Get[string] has the right signature. Then keep Get(key) (string, error) for the interface and add the typed read under its own name, such as GetAs[T].
- Follow-ups
-
- Why does Get[string] not count as the interface's Get?
- Would a function GetAs[T](s Source, key string) (T, error) be better than a method?
- How would you pass the typed read through an interface if a caller needs to?
From the lesson Generic methods in Go 1.27
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 2 minutes
-
Senior to staff
Your library, used by many modules, exports MapList and FilterList as package functions. With Go 1.27 you want them as methods of List. Plan the release: what changes for callers, what about their go lines, and how do you move them over without breaking anyone?
- What it tests
- The language version that generic methods need, the effect of raising a library's go line on its users, deprecation instead of removal, and what go fix's inline directive does and does not rewrite.
- First step
- Say first that generic methods force the library's go line to 1.27, so every user must build with Go 1.27 or later; that is a release of its own, announced as such. Add the methods, keep MapList and FilterList as one-line wrappers marked Deprecated, and remove them only in a new major version.
- Follow-ups
-
- What happens to a user whose module is built with Go 1.26?
- Can go fix move the callers automatically?
- When may the deprecated functions be removed?
From the lesson Generic methods in Go 1.27
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 2 minutes
-
Mid to senior
Review a Query[Row any] API with the methods Where(func(Row) bool) Query[Row], Select[V any](func(Row) V) []V and Sum() Row, which does not compile. Decide which operations stay methods, which become functions, and why.
- What it tests
- That a generic method takes its receiver's constraint as given, how its own type parameters can carry a constraint, and when a package function is still the clearer API.
- First step
- Start with the error: Sum needs + on Row, the method sees Row only with Query's own constraint, any, and a receiver cannot narrow it. Then sort the operations: the ones that work for every Row can stay methods, and the ones that need more of Row need another home.
- Follow-ups
-
- Why can the receiver not write Query[Row Number]?
- Which of the two Sum designs would you choose for this API?
- Does making Select a method change anything for callers on Go 1.26?
From the lesson Generic methods in Go 1.27
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 2 minutes
-
Mid to senior
A reporting package defines Merger with Merge(Merger) Merger. Its callers are full of type assertions, and merging two different report types panics now and then. Propose a generic design for Go 1.26 or later, and say what it costs.
- What it tests
- Seeing why a method that takes and returns the interface loses the type, writing a self-referential constraint, and knowing what generics cannot express, such as a list that mixes types.
- First step
- Show the cost of the plain interface: Merge must accept any Merger, so each implementation asserts its argument, and every caller asserts the result back. Then replace it with type Mergeable[M Mergeable[M]] interface { Merge(M) M } and generic functions such as MergeAll[M Mergeable[M]], which return M and refuse mixed types while compiling.
- Follow-ups
-
- What happens to code that kept a []Merger holding several report types?
- How did code write the same constraint before Go 1.26?
- Is the generic version faster as well?
From the lesson Advanced generics: inference and self-reference
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 2 minutes
-
Mid to senior
Users of your package's func Convert[From, To Number](xs []From) []To complain that every call must write both type arguments, as in Convert[int64, float64](fares). Explain why, and fix the API without breaking the code that already calls it.
- What it tests
- How partial type argument lists work, why a result-only type parameter belongs first, and how to change an exported generic function compatibly.
- First step
- Explain the cause: To appears only in the result, so it cannot be inferred, and a partial list can give only a prefix, which here is From. Reordering the parameters would break every existing Convert[int64, float64] call, so add a new function, To[Out, In Number](xs []In) []Out, and mark Convert deprecated.
- Follow-ups
-
- Why can Go not infer To from the variable the result is assigned to?
- Could Convert be changed in place in a new major version?
- How would you move the existing callers over?
From the lesson Advanced generics: inference and self-reference
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 2 minutes
-
Senior to staff
A generic LoadAll[T Loader](rows []Row) ([]T, error) calls t.Load(row) on each element of make([]T, len(rows)). Loader's Load has pointer receivers in every implementation. One team calls LoadAll[*Config] and gets nil pointer panics; another cannot compile LoadAll[Config]. Review and fix it.
- What it tests
- Method sets of T and *T, why a pointer type argument gives nil elements, the PT pattern with its inference, and when a function argument is the simpler design.
- First step
- Explain both failures from method sets and zero values: why Config does not satisfy Loader when every Load has a pointer receiver, and what make([]T, len(rows)) holds when T is *Config. Then look for a signature in which LoadAll creates real Config values and still calls Load on their addresses, with callers writing only the type they want back.
- Follow-ups
-
- How does Go infer PT when the caller writes only Config?
- What does the compiler say for a T whose pointer has no Load?
- When would you pass a load function instead?
From the lesson Advanced generics: inference and self-reference
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 2 minutes
-
Senior to staff
A refactor turned a pricing loop over []Item into Total[T Pricer](items []T) int, which calls it.Price() on each element. Its benchmark is now about twice as slow, and the team suspects generics in general. Explain what is happening and what you would change.
- What it tests
- GC shapes and dictionaries, what a method call through a type parameter costs, reading the compiler's inlining report, and the function-argument design, all backed by repeated benchmarks.
- First step
- Separate the cases first: an operator on a basic shape costs nothing extra, but a method called on a T is left to the dictionary. Then compare the old loop and the new one in the compiler's inlining report, go test -gcflags=-m, and benchmark a version that takes the price function as an argument.
- Follow-ups
-
- Why is the version with pointer type arguments slower still?
- How do you show the cause instead of guessing it?
- When would you keep a concrete copy of the loop?
From the lesson Generics in practice: performance and API design
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 2 minutes
-
Senior to staff
A team fears that generics will make their binary large: a generic cache is instantiated with 40 value types, 30 of them pointer types and 10 structs. How many compiled copies of its methods should they expect, and how would you check the real number?
- What it tests
- Knowing that gc compiles one body per GC shape rather than per type, which types share a shape, and how to count the instantiations in a binary with go tool nm.
- First step
- Explain the rule: gc compiles one body per GC shape, and all 30 pointer types share one shape, so they cost one copy of each method between them. Structs share a shape only with types of the same underlying type, so the 10 structs may cost up to 10 copies. Then measure: build the binary and list the instantiated symbols with go tool nm.
- Follow-ups
-
- What does each extra instantiation of the same shape still cost?
- Would storing values instead of pointers change the count?
- How do you find which generic functions take the most space?
From the lesson Generics in practice: performance and API design
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 2 minutes
-
Mid to senior
A pull request adds Repository[K comparable, V Entity[K], S Store[K, V]], a Store interface with one in-memory implementation, and an Entity constraint, for a service that stores one kind of record. Review it: what do you ask to remove, what may stay, and how do you keep behaviour safe while it changes?
- What it tests
- The guideline to add type parameters when a second type needs the same code, interfaces with one implementation, and refactoring behind tests that pin the behaviour.
- First step
- Show that every type parameter is fixed to a single type at the only call, so none makes the code serve more types, and that Store has one implementation. Then ask what the service needs today, and which tests pin that behaviour, before anything is removed.
- Follow-ups
-
- Which part would you keep if a second record type is planned for next quarter?
- How do the tests stay valid while the API changes?
- When does a Store interface become worth it?
From the lesson Generics in practice: performance and API design
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes