Tutorials Logic, IN info@tutorialslogic.com
Navigation
Home About Us Contact Us Blogs FAQs
Tutorials
All Tutorials
Services
Academic Projects Resume Writing Website Development
Practice
Quiz Challenge Interview Questions Certification Practice
Tools
Online Compiler JSON Formatter Regex Tester CSS Unit Converter Color Picker
Compiler Tools

Golang Interfaces: Implicit Contracts and Polymorphism

What Interfaces Mean in Golang

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.

Implicit Interface
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")
}

Small Interfaces Are Preferred

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 SizeEffect
One methodEasy to reuse and test
Two or three related methodsGood for focused behavior
Many unrelated methodsOften too broad and hard to mock
Focused Interface
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
}

Where to Declare Interfaces

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.

Consumer-Owned Interface
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 and Testing

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.

Fake Implementation
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")
    }
}

The Empty Interface and any

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.

TypeMeaningAdvice
interface{}Any value can fitOlder spelling
anyAlias for interface{}Preferred modern spelling
Specific interfaceOnly values with required methods fitBest for behavior contracts

Type Assertions and Type Switches

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.

Safe Type Assertion
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"
    }
}
Key Takeaways
  • Interfaces describe behavior, not data storage.
  • Types satisfy interfaces implicitly when they have the required methods.
  • Prefer small interfaces owned by the consumer.
  • Interfaces are valuable for tests because they allow simple fake implementations.
  • Use any only when values are truly unknown.
  • Use comma-ok assertions when a type may not match.

Ready to Level Up Your Skills?

Explore 500+ free tutorials across 20+ languages and frameworks.