Advanced Go Interfaces in 2026: Composition, Type Assertions and Interview Questions
Master Go interface composition, type assertions, and self-referential generics from Go 1.26. Includes practical examples and technical interview questions.

Go interfaces define behavior without prescribing implementation, making them central to writing flexible, testable code. Go 1.26 extends this power with self-referential generics, new reflection iterators, and type-safe error handling via errors.AsType. This guide covers interface composition, type assertions, embedding patterns, and the questions that come up in technical interviews.
Generic types can now reference themselves in their type parameter list: type Adder[A Adder[A]] interface { Add(A) A }. This F-bounded polymorphism pattern enables interfaces that constrain return types to the implementing type itself.
Interface Composition and Embedding Patterns
Interface embedding combines multiple interfaces into a single contract. The result is a new interface that requires all methods from its embedded interfaces. This pattern avoids duplication and creates clear abstraction boundaries.
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
type Closer interface {
Close() error
}
// Composed interface embedding three interfaces
type ReadWriteCloser interface {
Reader
Writer
Closer
}Any type implementing Read, Write, and Close methods automatically satisfies ReadWriteCloser. The Go standard library uses this pattern extensively in the io package.
A common interview question asks candidates to explain why Go prefers small, focused interfaces. The answer lies in composability: io.Reader appears in hundreds of functions because it demands exactly one method. Larger interfaces create tighter coupling and reduce reuse.
Type Assertions: Syntax and Safety
Type assertions extract a concrete type from an interface value. The two-value form prevents panics by returning a boolean indicating success.
func processValue(v interface{}) {
// Two-value assertion: safe, no panic
if str, ok := v.(string); ok {
fmt.Printf("String value: %s\n", str)
return
}
// Type switch for multiple types
switch val := v.(type) {
case int:
fmt.Printf("Integer: %d\n", val)
case float64:
fmt.Printf("Float: %.2f\n", val)
case []byte:
fmt.Printf("Bytes: %x\n", val)
default:
fmt.Printf("Unknown type: %T\n", val)
}
}Type switches handle multiple possible types cleanly. Each case binds val to the asserted type within its block, eliminating the need for separate assertions.
Single-value assertions like str := v.(string) panic if the assertion fails. Production code should always use the two-value form or a type switch.
Interviewers often ask about the performance of type assertions. The runtime performs a single comparison of type descriptors, making assertions inexpensive. The cost increases with interface values that wrap pointers to large structs due to indirection, but the assertion itself remains O(1).
Self-Referential Generics in Go 1.26
Go 1.26 introduced self-referential type parameters, enabling interfaces where methods must return the implementing type. This pattern, sometimes called F-bounded polymorphism, solves a long-standing limitation.
// Self-referential interface: methods return the same type
type Builder[B Builder[B]] interface {
WithName(name string) B
WithAge(age int) B
Build() string
}
type PersonBuilder struct {
name string
age int
}
func (p PersonBuilder) WithName(name string) PersonBuilder {
p.name = name
return p
}
func (p PersonBuilder) WithAge(age int) PersonBuilder {
p.age = age
return p
}
func (p PersonBuilder) Build() string {
return fmt.Sprintf("%s, %d years old", p.name, p.age)
}
// Generic function using the self-referential constraint
func configure[B Builder[B]](b B, name string, age int) string {
return b.WithName(name).WithAge(age).Build()
}The constraint Builder[B] ensures that WithName and WithAge return B, not a generic Builder. Without this, the return type would be the interface, losing the concrete type information and breaking method chaining.
Math types benefit from this pattern. An Addable[A Addable[A]] interface ensures Add(A) A returns the same numeric type, preventing accidental mixing of BigInt and Decimal values.
errors.AsType: Type-Safe Error Unwrapping
Go 1.26 added errors.AsType, replacing the pointer-dance pattern of errors.As. The generic version returns the unwrapped error directly.
type ValidationError struct {
Field string
Message string
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("validation failed on %s: %s", e.Field, e.Message)
}
func handleError(err error) {
// Go 1.26: generic type-safe unwrapping
if valErr, ok := errors.AsType[*ValidationError](err); ok {
log.Printf("Validation error on field %s\n", valErr.Field)
return
}
// Before Go 1.26: pointer-based unwrapping
// var valErr *ValidationError
// if errors.As(err, &valErr) { ... }
log.Printf("Unexpected error: %v\n", err)
}The new API eliminates the separate variable declaration and makes the target type explicit in the function call. Error chains are searched the same way as before; only the interface changed.
Ready to ace your Go interviews?
Practice with our interactive simulators, flashcards, and technical tests.
Reflection Iterators for Interface Inspection
Go 1.26 added iterator methods to the reflect package. Type.Methods() and Value.Methods() return iterators for ranging over methods, replacing index-based loops.
import "reflect"
func inspectInterface(v interface{}) {
t := reflect.TypeOf(v)
// Go 1.26: iterator-based method inspection
fmt.Printf("Type %s methods:\n", t.Name())
for method := range t.Methods() {
fmt.Printf(" %s: %s\n", method.Name, method.Type)
}
// For struct fields (also new in 1.26)
if t.Kind() == reflect.Struct {
for field := range t.Fields() {
fmt.Printf(" Field: %s (%s)\n", field.Name, field.Type)
}
}
}The iterator pattern aligns with Go 1.23's range-over-function feature. Code becomes more readable, and there is no performance penalty: iterators yield values lazily.
Interface Satisfaction at Compile Time
Go checks interface satisfaction at compile time when assigning a concrete value to an interface variable. Explicit checks using blank identifier assignments catch errors early in large codebases.
type Storage interface {
Save(key string, data []byte) error
Load(key string) ([]byte, error)
Delete(key string) error
}
type FileStorage struct {
basePath string
}
// Compile-time check: fails if FileStorage misses a method
var _ Storage = (*FileStorage)(nil)
func (f *FileStorage) Save(key string, data []byte) error {
path := filepath.Join(f.basePath, key)
return os.WriteFile(path, data, 0644)
}
func (f *FileStorage) Load(key string) ([]byte, error) {
path := filepath.Join(f.basePath, key)
return os.ReadFile(path)
}
func (f *FileStorage) Delete(key string) error {
return os.Remove(filepath.Join(f.basePath, key))
}The line var _ Storage = (*FileStorage)(nil) compiles only if *FileStorage satisfies Storage. This pattern catches missing methods immediately rather than at runtime when a value is assigned.
The Empty Interface and Type Constraints
The empty interface interface{} accepts any value, while any is its alias since Go 1.18. Generic constraints provide compile-time type safety without runtime assertions.
import "golang.org/x/exp/constraints"
// Generic function with numeric constraint
func Sum[T constraints.Integer | constraints.Float](values []T) T {
var total T
for _, v := range values {
total += v
}
return total
}
// Comparable constraint for map keys
func Contains[K comparable, V any](m map[K]V, key K) bool {
_, exists := m[key]
return exists
}Preferring generics over interface{} eliminates type assertions and catches type mismatches at compile time. The tradeoff is added complexity in function signatures, so generics fit best when a function genuinely operates on multiple types.
Technical Interview Questions on Go Interfaces
Interviewers test interface knowledge at multiple levels. Here are common questions with concise answers.
Q: What happens when you call a method on a nil interface value versus a nil concrete value inside an interface?
A nil interface has no type and no value; calling any method panics. A non-nil interface holding a nil pointer has a type; the method executes with a nil receiver. This behavior allows patterns like (*bytes.Buffer)(nil).String() returning an empty string.
Q: How does interface comparison work?
Two interface values are equal if they have the same dynamic type and equal dynamic values. Comparing interfaces with uncomparable types (slices, maps, functions) panics at runtime.
Q: When should a method use a pointer receiver versus a value receiver?
Pointer receivers allow mutation and avoid copying large structs. Value receivers are safe for concurrent use and work with both values and pointers. If any method needs a pointer receiver, all methods on that type should use pointer receivers for consistency, since only *T satisfies an interface requiring a pointer-receiver method.
type Counter struct {
count int
}
// Pointer receiver: modifies the struct
func (c *Counter) Increment() {
c.count++
}
// Value receiver: read-only operation
func (c Counter) Value() int {
return c.count
}
type Incrementer interface {
Increment()
}
func main() {
var c Counter
// var i Incrementer = c // Compile error: Counter lacks Increment
var i Incrementer = &c // OK: *Counter has Increment
i.Increment()
}Q: Explain the "accept interfaces, return structs" principle.
Functions accepting interfaces decouple from concrete implementations, enabling testing with mocks. Returning concrete types gives callers full access to the type's methods without type assertions. Returning an interface hides future method additions behind a type assertion.
For more Go interview preparation, see the Go concurrency questions module and the testing module.
When asked to design an interface, start with the single-method case. Expand only when multiple methods are always called together. The standard library's Stringer, Reader, and Handler interfaces each define one method.
What Senior Go Developers Should Know About Interfaces
- Interface composition through embedding creates flexible contracts without inheritance hierarchies. Keep interfaces small: one to three methods covers most use cases.
- Type assertions and type switches extract concrete types safely. Always use the two-value form or a switch in production code to avoid panics.
- Go 1.26's self-referential generics enable builder patterns and math types where methods must return the implementing type.
errors.AsTypesimplifies error unwrapping with a generic, type-safe API. Use it in new code targeting Go 1.26 or later.- Compile-time interface checks with
var _ Interface = (*Type)(nil)catch missing methods before tests run. - Prefer generics over
interface{}when type safety at compile time outweighs the complexity in signatures. - The "accept interfaces, return structs" guideline keeps APIs flexible for callers while exposing full functionality.
Start practicing!
Test your knowledge with our interview simulators and technical tests.
Can you spot the bug in Go?
One real snippet, one hidden bug, one attempt a day. No account needed to try.

Written by
Anthony Fillion-MailletFounder of SharpSkill
Full-stack developer for over 10 years. Runs SharpSkill and answers for everything published here.
Updated on September 10, 2026
Tags
Share
Related articles

Go SIMD and ArchSIMD Package in 2026: Performance Optimization and Interview Questions
Master Go 1.26's simd/archsimd package for native vector operations. Learn to implement SIMD-optimized code with 30-50% performance gains, understand CPU feature detection, and prepare for Go interview questions on parallel processing.

Go 1.26 Interview: Green Tea GC, go fix and Stack Optimizations
Prepare for Go 1.26 interview questions covering the Green Tea garbage collector, revamped go fix tool, stack allocation optimizations, and key performance improvements.

Go Context Package in 2026: Cancellation, Timeouts and Interview Questions
Master Go's context package for managing cancellation, deadlines, and request-scoped values. Complete guide with practical examples and interview questions.