An interface describes behavior through method signatures. A type satisfies an interface automatically when it has the required methods, so there is no implements keyword.
This implicit model keeps packages loosely coupled. A function can ask for the behavior it needs instead of depending on one concrete struct. That makes code easier to test, replace, and reuse.
package main
import "fmt"
type Notifier interface {
Notify(message string) error
}
type EmailNotifier struct{}
func (EmailNotifier) Notify(message string) error {
fmt.Println("email:", message)
return nil
}
func sendWelcome(n Notifier) {
_ = n.Notify("Welcome to Golang")
}
Golang code usually favors small interfaces. One-method interfaces such as readers, writers, validators, loggers, and notifiers are easy to implement and easy to fake in tests.
Large interfaces often force types to implement behavior they do not naturally own. If an interface has many unrelated methods, split it by use case so each consumer asks only for what it needs.
| Interface Size | Effect |
|---|---|
| One method | Easy to reuse and test |
| Two or three related methods | Good for focused behavior |
| Many unrelated methods | Often too broad and hard to mock |
type SlugChecker interface {
Exists(slug string) (bool, error)
}
func ValidateUniqueSlug(checker SlugChecker, slug string) error {
exists, err := checker.Exists(slug)
if err != nil {
return err
}
if exists {
return errors.New("slug already exists")
}
return nil
}
A useful Golang habit is to declare an interface near the code that consumes it, not always near the concrete implementation. The consumer knows the exact behavior it needs.
This keeps dependency direction clean. A package that sends notifications can define the small notification contract without depending on a specific email, SMS, or push implementation.
type UserStore interface {
FindByEmail(email string) (User, error)
}
type UserService struct {
store UserStore
}
func (s UserService) CanRegister(email string) (bool, error) {
_, err := s.store.FindByEmail(email)
if errors.Is(err, ErrNotFound) {
return true, nil
}
if err != nil {
return false, err
}
return false, nil
}
Interfaces make tests easier because a test can provide a small fake implementation instead of connecting to a real database, email provider, queue, or payment gateway.
The best test doubles are simple. A fake should model the behavior needed by the test, not become a second full implementation of the production dependency.
type fakeUserStore struct {
user User
err error
}
func (f fakeUserStore) FindByEmail(email string) (User, error) {
return f.user, f.err
}
func TestCanRegisterWhenMissing(t *testing.T) {
service := UserService{
store: fakeUserStore{err: ErrNotFound},
}
ok, err := service.CanRegister("new@example.com")
if err != nil || !ok {
t.Fatalf("expected registration to be allowed")
}
}
interface{} is an interface with no required methods, so every value satisfies it. Modern Golang provides the alias any, which means the same thing and is easier to read.
Use any at boundaries where values are truly unknown, such as decoded JSON, logging fields, or generic containers. Avoid using it in core business code when a specific type or interface would be clearer.
| Type | Meaning | Advice |
|---|---|---|
interface{} | Any value can fit | Older spelling |
any | Alias for interface{} | Preferred modern spelling |
| Specific interface | Only values with required methods fit | Best for behavior contracts |
A type assertion extracts a concrete value from an interface. Use the comma-ok form when the type may not match. Without comma-ok, a failed assertion panics.
A type switch is useful at boundaries where unknown values arrive, such as decoding mixed input. In core business code, repeated type switches may mean a better interface or data model is needed.
func printLength(value any) {
text, ok := value.(string)
if !ok {
return
}
fmt.Println(len(text))
}
func describe(value any) string {
switch v := value.(type) {
case string:
return "string: " + v
case int:
return fmt.Sprintf("int: %d", v)
default:
return "unknown"
}
}
any only when values are truly unknown.
Explore 500+ free tutorials across 20+ languages and frameworks.