Language Internals
Part 4 of 11 · Go Language ProficiencyInterfaces, Generics & Type Sets
Implicit interfaces, generics, type sets, and generic methods in Go 1.27.
- 1Gist
- 2Maps
- 3Q&A
- 4Sandbox
Voice readout needs Web Speech Synthesis in this browser.
Question ladder
L1
How does Point satisfy Stringer?
Answer
It has String. No implements clause. A compile-time assertion can document the fact.
L2
Why keep interfaces small?
Answer
A one-method interface is easy to satisfy from another package. A large interface becomes a wall.
L3
What does a tilde mean in a type set?
Answer
The underlying type matches. tilde-int accepts int and defined types whose underlying type is int.
L4
When do you need comparable?
Answer
When the type parameter is used as a map key or with equality.
L5
What changed for methods in Go 1.27?
Answer
Methods may declare type parameters. Before 1.27 only package-level functions and generic types could be generic.
L6
When is an interface not equal to nil?
Answer
When it holds a concrete type, even if that concrete value is a nil pointer.
L7
How does that compare with a Python Protocol?
Answer
Both are structural. Go checks at compile time. A Protocol is checked by a type checker, not by the interpreter.
Failure modes
Nil pointer in an interface
A function returns a nil pointer as an error interface, and the caller sees a non-nil error.
Interface too wide
Callers depend on twenty methods and no test double can satisfy them.
Constraint too tight
A named integer type is rejected because the type set forgot the tilde.
Map key without comparable
The compiler rejects the key. Add comparable or stop using the type as a key.
Misconceptions
You must write implements.
Satisfaction is implicit. An assertion variable is optional documentation.
Generics arrived in Go 1.27.
Type parameters arrived in 1.18. 1.27 extends them to methods.
A nil pointer error is a nil interface.
The interface stores the pointer type, so it is not nil.
any is a special type.
any is an alias for an empty interface.
Interviewer traps
Returning a nil concrete pointer as error.
Return an untyped nil, or the caller cannot use a simple nil check.
Writing a type set without the tilde and calling it the same.
The tilde includes defined types with that underlying type.
Putting a generic method on a type that only needed a function.
A package-level function is still the smaller tool when there is no receiver state.
Design scenario
Same prompt for every reader.
Requirements
A one-method interface, a type set with tildes, comparable only where a map key exists.
Traffic / scale
Library code called from several services.
Latency
Monomorphized generics are a compile-time cost, not an interface call.
Consistency
A nil logger pointer must not look like a present logger.
Availability
A too-wide interface blocks a test double and slows a change.
Failure assumptions
- Every logger must implement a 12-method interface.
- A nil pointer error is checked with err == nil and the check passes.
Constraints
- Do not invent a method signature beyond the 1.27 rule that methods may take type parameters.
- Show the nil-interface case in words.
Prompt
Accept any logger with one method, and sum a slice of integers without listing every named integer type.
Structural match versus a declared implements
Prefer
Method set
The type matches or it does not. Another package can satisfy your interface without importing it.
- Small interfaces travel.
- Type sets constrain operators.
- The nil pair is a real bug.
Alternative
Nominal implements
TypeScript often writes implements. Python Protocols are closer to Go, and the interpreter still does not check them.
- A class can claim a type it only partly matches.
- None is one value, not a type-and-value pair.
- Generics have been normal in both languages for years.
Overview
Interfaces are implicit. Generics add type parameters and type sets. Go 1.27.1 lets a method declare its own type parameters. Until then, only package-level functions and generic types could be generic.
Decisions
- 1
Step 1 Define a small interface where it is consumed
- nextStep 2 Concrete type has the methods - no implements keyword
- 2
Step 2 Concrete type has the methods - no implements keyword
- nextStep 3 Same logic for many types
- ?
Step 3 Same logic for many types
- same algorithm, many typesStep 4a Type parameter with a constraint like Number
- behavior differs per typeStep 4b Interface value - dynamic dispatch
- 4
Step 4a Type parameter with a constraint like Number
- nextStep 5 Go 1.27 - methods may declare their own type parameters
- 5
Step 4b Interface value - dynamic dispatch
- return a typed nil pointer as errorFailure path - interface is non-nil, err != nil is true
- 6
Step 5 Go 1.27 - methods may declare their own type parameters
- 7
Failure path - interface is non-nil, err != nil is true
Lesson map
Interfaces, Generics & Type Sets
Implicit interfaces, generics, type sets, and generic methods in Go 1.27.
Architecture. Step 1 Define a small interface where it is consumed Ready. Step 2 Concrete type has the methods - no implements keyword Ready. Step 3 Same logic for many types Ready. Step 4a Type parameter with a constraint like Number Ready. Step 4b Interface value - dynamic dispatch Ready. Step 5 Go 1.27 - methods may declare their own type parameters Ready. Failure path - interface is non-nil, err != nil is true Ready
Select a node to see why it exists, or an edge to see the protocol, direction, effect, and consequence.
Mermaid export
flowchart TB A["Step 1 Define a small interface where it is consumed Ready"] B["Step 2 Concrete type has the methods - no implements keyword Ready"] C["Step 3 Same logic for many types Ready"] D["Step 4a Type parameter with a constraint like Number Ready"] E["Step 4b Interface value - dynamic dispatch Ready"] G["Step 5 Go 1.27 - methods may declare their own type parameters Ready"] X["Failure path - interface is non-nil, err != nil is true Ready"] A -->|continues| B B -->|continues| C C -->|same algorithm, many types| D C -->|behavior differs per type| E D -->|continues| G E -->|return a typed nil pointer as error| X
Press Run. Snippets must be self-contained — no network, files, or native modules.
Python's __str__ is structural in practice. Go's String method is structural at compile time.
Rosetta — implicit Stringer
package geo
import "fmt"
type Stringer interface {
String() string
}
type Point struct{ X, Y int }
func (p Point) String() string {
return fmt.Sprintf("(%d,%d)", p.X, p.Y)
}
var _ Stringer = Point{}interface Stringer {
toString(): string;
}
class Point implements Stringer {
constructor(public x: number, public y: number) {}
toString() {
return `(${this.x},${this.y})`;
}
}from typing import Protocol
class Stringer(Protocol):
def __str__(self) -> str: ...
class Point:
def __init__(self, x: int, y: int) -> None:
self.x, self.y = x, y
def __str__(self) -> str:
return f"({self.x},{self.y})"var _ Stringer = Point{} fails compilation if Point drifts. TypeScript's implements is closer to that assertion than to a runtime check. A nil *Point stored in a Stringer is not a nil interface.
Rosetta — type sets
package gen
type Number interface {
~int | ~int64 | ~float64
}
func Sum[T Number](xs []T) T {
var s T
for _, v := range xs {
s += v
}
return s
}
func Keys[M ~map[K]V, K comparable, V any](m M) []K {
out := make([]K, 0, len(m))
for k := range m {
out = append(out, k)
}
return out
}function sum(xs: number[]): number {
return xs.reduce((a, b) => a + b, 0);
}
function keys<K extends string, V>(m: Record<K, V>): K[] {
return Object.keys(m) as K[];
}from typing import Iterable, TypeVar
T = TypeVar("T", int, float)
def sum_nums(xs: Iterable[T]) -> T:
total: T = 0 # type: ignore[assignment]
for value in xs:
total = total + value # type: ignore[operator]
return totalcomparable is what map keys need. The tilde accepts named types whose underlying type is in the set. Go 1.27 adds type parameters on methods. Reach for a function first when the operation does not need receiver state.
Interview Q&A
Why is there no implements keyword?
Answer
So a type can satisfy an interface defined in a package it does not import. Keep the interface to the methods you call.
What changed for methods in 1.27?
Answer
Methods can declare type parameters. Package-level generic functions have existed since 1.18.
What does the tilde do?
Answer
It matches the underlying type, so a defined integer type still passes a numeric constraint.
Why comparable on Keys?
Answer
The key type is used as a map key. Map keys must be comparable.
When is err != nil a lie?
Answer
When the concrete value is a typed nil. Return a bare nil error instead of a nil pointer of an error type.
Is any the same as interface with no methods?
Answer
Yes. any is that alias. It says nothing about behavior.
How is a TypeScript interface different?
Answer
It is also structural, but classes often write implements, and the emit erases it. Go checks the method set when you assign the value.
How is a Protocol different?
Answer
A type checker can verify it. CPython will still call the method if it exists. Go refuses the program.
Pitfalls
- Returning
(*MyError)(nil)from a function whose result type iserror. - A type set of plain
intthat rejectstype ID int. - An interface with methods nobody calls.
- Using a generic method where a two-line function was enough.
Take a 6-method service interface. Name the one method a handler actually calls, and say whether the value stored in it could be a nil pointer.