Go interfaces interview questions, with answers
Interfaces are how Go gets polymorphism without classes, and they work differently from Java's or C#'s: a type never declares the interfaces it implements, it simply has the methods. That one difference changes how Go programs are designed, and interviewers follow it through: how a value is recovered from an interface, when a type satisfies one and when it does not, and when generics are the better tool.
The answers below cover implementation, the empty interface, type switches and assertions, compile-time checks, generics and method sets, with code built and run on Go 1.27. Then take the free Go diagnostic — ten questions across every Go topic in the bank — to see which of these you can explain but not yet predict.
The questions, with answers
1.How does a type implement an interface in Go?
In short: Implicitly: a type implements an interface as soon as it has all of the interface's methods, with no implements keyword or declaration.
An interface type is a set of method signatures, and any type whose method set includes all of them satisfies the interface automatically. Rect below never mentions Shape; it just has an Area method with the right signature, so a Rect can be stored in a Shape variable and called through it. Because satisfaction is structural, an interface can be declared after the types that satisfy it, even in a different package, and small interfaces with one or two methods, such as io.Reader and fmt.Stringer, are the idiomatic style. The compiler checks the match wherever a value is converted to the interface.
type Shape interface { Area() float64 } type Rect struct { W, H float64 } func (r Rect) Area() float64 { return r.W * r.H } func main() { var s Shape = Rect{3, 4} fmt.Println(s.Area()) // 12 }
2.What is the empty interface, any, in Go?
In short: any, an alias for interface{}, is the interface with no methods, so a value of any type satisfies it; using it gives up compile-time type checking.
An interface with no methods is satisfied by every type, so a variable of type interface{}, or any, its alias since Go 1.18, can hold anything: an int, a string, a struct, a pointer. fmt.Println takes ...any, which is how it prints every kind of value, and %T reports the dynamic type stored in each one. The cost is that the compiler no longer knows what the value is, so using it requires a type assertion or a type switch, and mistakes surface at run time. Since generics arrived, any is less needed for containers and algorithms; it remains the right choice for truly heterogeneous data such as decoded JSON.
vals := []any{1, "two", 3.0} for _, v := range vals { fmt.Printf("%T ", v) } fmt.Println() // int string float64
3.What is a type switch in Go?
In short: A switch on v.(type) that picks a case by the dynamic type stored in an interface value, binding a variable of that type in each case.
A type switch, switch x := v.(type), compares the dynamic type of the interface value v against each case. In a case that names one type, x has that type, so in the string case below len(x) works without any conversion. A case listing several types, or the default case, leaves x with the interface's type. A nil case matches a nil interface value. Type switches are how code handles a closed set of possibilities stored in an interface, such as the different node types of a syntax tree, and they are clearer and safer than a chain of separate type assertions.
func kind(v any) string { switch x := v.(type) { case int: return "int" case string: return "string of " + fmt.Sprint(len(x)) } return "other" } func main() { fmt.Println(kind(5)) // int fmt.Println(kind("hey")) // string of 3 fmt.Println(kind(2.5)) // other }
4.What is a type assertion in Go, and how do you make one safely?
In short: v.(T) extracts the concrete T stored in an interface value; the two-value form, x, ok := v.(T), reports failure with ok instead of panicking.
A type assertion asks whether the interface value v holds a T and, if it does, returns that T. The single-value form, x := v.(T), panics when the dynamic type is not T, so it belongs only where the type is certain. The two-value form, x, ok := v.(T), never panics: on failure, x is T's zero value and ok is false, which makes it the right choice for data whose type is not known in advance. T can also be an interface type, in which case the assertion checks whether the stored value implements it, which is how code tests for optional behaviour such as an io.WriterTo.
var v any = 4.5 f, ok := v.(float64) fmt.Println(f, ok) // 4.5 true _, ok = v.(string) fmt.Println(ok) // false
5.How do you check at compile time that a Go type implements an interface?
In short: Assign a value of the type to a blank variable of the interface type, var _ Iface = T{}, which fails to compile if a method is missing.
Because interfaces are satisfied implicitly, a type that is meant to implement one can silently stop doing so when a method is renamed or its signature changes, and the error only appears where the value is used, possibly in another package. A package-level declaration such as var _ Stringer = ID(0), or var _ Iface = (*T)(nil) for pointer receivers, turns that into an immediate compile error at the type's definition and costs nothing at run time. In the code, ID satisfies fmt's Stringer interface as well, which is why Println prints #7 instead of 7.
type Stringer interface { String() string } type ID int func (i ID) String() string { s := fmt.Sprint(int(i)) return "#" + s } var _ Stringer = ID(0) func main() { fmt.Println(ID(7)) // #7 }
6.When should you use generics instead of interfaces in Go?
In short: Use generics when code is the same for many types and should keep each type exactly; use interfaces when values of different types must be handled through shared behaviour.
An interface parameter accepts any type with the right methods, but inside the function the value's concrete type is hidden, and a result typed as the interface loses it too. Type parameters, available since Go 1.18, keep the type: Sum below returns an int for a []int and a float64 for a []float64, checked at compile time. A constraint such as Num lists the types allowed, and the ~ before int also admits named types whose underlying type is int. Generics suit containers and algorithms that work the same on many types; interfaces remain the tool for behaviour, such as anything with a Read method, and for collections of mixed types.
type Num interface { ~int | ~float64 } func Sum[T Num](xs []T) T { var t T for _, x := range xs { t += x } return t } func main() { a := []int{1, 2} fmt.Println(Sum(a)) // 3 b := []float64{0.5, 0.25} fmt.Println(Sum(b)) // 0.75 }
7.What is a method set in Go, and why does it matter for interfaces?
In short: A type's method set is the methods callable on it: a value type T has only its value-receiver methods, while *T has both, so only *T satisfies interfaces needing pointer methods.
Every type has a method set, and interface satisfaction is decided by it. The method set of a struct type T contains the methods declared with a value receiver; the method set of *T contains those plus the ones declared with a pointer receiver. Ping below has a pointer receiver, so *Srv implements Pinger and &Srv{} can be stored in one, while Srv{} could not. The rule exists because an interface holds a copy of a value, and a pointer method called on that copy could not modify the original. Calling a pointer method directly on an addressable variable still works, because Go takes the address for you.
type Pinger interface { Ping() string } type Srv struct{} func (s *Srv) Ping() string { return "pong" } func main() { var p Pinger = &Srv{} fmt.Println(p.Ping()) // pong }
How the diagnostic asks it
One question from the Go bank, exactly as a sitting would show it. The bank has 4 on interfaces and 30 across Go.
What happens when this Go program is compiled?
package main import "fmt" type Shape interface{ Area() float64 } type Sq struct{ s float64 } func (q *Sq) Area() float64 { return q.s * q.s } func main() { var sh Shape = Sq{2} fmt.Println(sh.Area()) }
- 1It compiles and prints 4
- 2It compiles, but panics at run time because Sq does not implement Shape
- 3It does not compile: Area has a pointer receiver, so only *Sq implements Shapecorrect
- 4It does not compile: Sq never declares that it implements Shape
A type's method set decides which interfaces it satisfies. Area is declared with a pointer receiver, so it belongs to the method set of *Sq only; the value type Sq does not have it. Assigning Sq{2} to a Shape is therefore a compile error: Sq does not implement Shape (method Area has pointer receiver). Writing &Sq{2} fixes it and prints 4. Calling q.Area() directly on an addressable Sq variable would work, because Go takes the address for you, but an interface stores a copy that is not addressable, so no automatic & happens. Go interfaces are never declared: satisfying them is implicit, so that option's reason is wrong. The check happens at compile time, not with a panic.
Measure it
Reading answers tells you what’s true. A diagnostic tells you what you get wrong.
10 Go questions across its topics, easy to hard, about fifteen minutes. You get a readiness figure with the arithmetic shown, the topics you missed named, and a practice set sized for today. Free: 1 diagnostic a month and 15 problems a day. No card.