WebRTC Explained
A practical guide to real-time audio, video, data channels, infrastructure choices, security, and production-ready implementation.
·
7 min read
Go interfaces are how you describe behaviour without naming a concrete type. You write the methods you need; any type that has those methods satisfies the interface with no implements keyword and no ceremony. That design choice shapes almost every good Go API you will touch.
In Go, a type satisfies an interface by having the right method set. There is no declaration on the type itself. The compiler checks compatibility at the call site or when you assign to an interface value.
type Greeter interface {
Greet() string
}
type Person struct{ Name string }
func (p Person) Greet() string {
return "hello, " + p.Name
}
var g Greeter = Person{Name: "Ada"}
Person never mentions Greeter. The assignment works because the method sets match. That keeps packages loosely coupled: you can introduce an interface in the consumer without changing the producer, which is the opposite of many OOP languages where the producer must know the contract in advance.
Prefer defining interfaces where they are used, not where types are declared. A package that returns a concrete *os.File is often easier to consume than one that invents a fat interface nobody asked for.
The standard library’s best interfaces are tiny. io.Reader has one method:
type Reader interface {
Read(p []byte) (n int, err error)
}
Anything that can fill a byte slice that way is a reader: files, network connections, buffers, HTTP bodies. Functions accept io.Reader and remain useful far beyond the original author’s imagination. I reach for io.Reader-shaped contracts before inventing domain-specific ones.
Compose small interfaces rather than growing one large surface. io.ReadCloser is just Reader plus Closer. Callers who only need to read do not pay for close semantics they ignore. When you design your own contracts, start with one or two methods and stop. If a third method only serves a single call site, that call site probably wants a concrete type or a separate interface.
An interface value holds a type and a value. When you need the concrete type back, assert or switch.
func describe(v any) string {
if s, ok := v.(string); ok {
return "string: " + s
}
switch x := v.(type) {
case error:
return "error: " + x.Error()
case int:
return fmt.Sprintf("int: %d", x)
default:
return "unknown"
}
}
Use a type assertion with the two-value form (val, ok := x.(T)) when you expect one type and want to handle failure without a panic. Use a type switch when several concrete types need different handling, especially errors or values stored as any (historically interface{}). Prefer asserting to a small interface over asserting to a concrete struct when you only need a method.
The classic trap is a nil concrete pointer stored in a non-nil interface. An interface is nil only when both its type and value are unset. A typed nil pointer still makes the interface non-nil, so if err != nil can surprise you after a helper returns a nil pointer converted to error. Return a bare nil from functions whose result type is the interface, not a nil pointer of a concrete error type.
Another habit that bites: interfaces with many methods, or interfaces defined for mocking in the same package as the implementation. You end up with brittle stubs and APIs that are hard to extend. Keep production interfaces small and let tests invent narrower fakes at the call site.
Avoid empty interfaces as a substitute for generics. Since Go 1.18, prefer type parameters when you need compile-time type safety across similar algorithms. Keep any for truly heterogeneous bags of values, such as formatting or reflection-style helpers.
ok form.default and new cases.Interfaces and concrete types work together: return concrete types from constructors when you can, and accept interfaces at the boundaries where you need substitutability. Assertions and switches are tools for peeling an interface open, not for designing everyday APIs.
Use Go interfaces when callers need to plug in behaviour you do not control. Small, stable method sets help, as do contracts the standard library already defined (io.Reader, fmt.Stringer, error). Stay with a concrete type when there is one implementation or you need exported fields. Skip an interface that would exist only to satisfy a test double.
Design for the call site: if two methods would do, do not invent five.
If you are shaping Go services or platform APIs and want a second pair of eyes on interface boundaries, get in touch.
A practical guide to real-time audio, video, data channels, infrastructure choices, security, and production-ready implementation.
·
7 min read
A practical guide to designing resilient event-driven platforms that scale cleanly, recover safely, and support modern digital services.
·
7 min read
A practical guide to Deno, its secure runtime, TypeScript support, tooling, deployment patterns, and when teams should adopt it.
·
7 min read
A practical guide to Bun.js performance, tooling, migration strategy, and how Eight Mile can help teams adopt it safely.
·
8 min read
A practical beginner guide to Flutter widgets, Dart syntax, state, layouts, and building reliable cross-platform apps.
·
2 min read
Learn how Model View Controller separates data, interface, and application logic to make software easier to build and maintain.
·
2 min read
A practical guide to choosing the right tests, avoiding brittle suites, and shipping software with confidence.
·
3 min read
Practical patterns for writing simple, readable, maintainable Go that fits the language.
·
4 min read
Practical habits that make Python code clearer, safer, and more maintainable.
·
4 min read
How to build command line programs in Go that read from pipes, fail properly, handle signals and ship as one binary anyone can run.
·
6 min read
A practical tour of the core JavaScript ideas — types, scope, functions, objects, and asynchrony — that make every framework you learn afterwards feel obvious.
·
6 min read
What object-oriented programming is, its four core principles, when to use it, and worked examples in Python, Java and JavaScript.
·
4 min read