Chapter 10 ended with a map[int]Task as the backing store. It works well, but the code using that map is locked to a specific data structure. This chapter introduces Go interfaces. Interfaces are the mechanism that lets you break that lock. We will define a TaskStore interface in store.go and show two completely different types satisfying it, without using an implements keyword anywhere.
We are adding two new files. store.go holds the TaskStore interface and two concrete implementations. interfaces.go holds a demo that calls both implementations through the exact same function. We also add one method to task.go so that Task satisfies Go's standard fmt.Stringer interface.

By the end of this chapter, you will know how to:
implements keyword)fmt.Stringer as a real standard-library example of an interface your types satisfyanyCheck out the finish branch:
bashgit checkout 11-interfaces-finish
Interface. A named set of method signatures. Any type that has all of those methods automatically satisfies the interface. Go checks the methods, not a declaration.
Implicit satisfaction. There is no implements keyword in Go. You never write class Foo implements Bar. If your type's method set includes all the methods of an interface, it satisfies that interface. The compiler verifies this at the point of use.
Seam. A boundary in code where one part can be swapped without changing the other parts. The TaskStore interface is the central seam of this course. The HTTP handlers and the runStore function talk only to the interface. Replacing InMemoryStore with PostgresStore later will only change the wiring, not the callers.
Type assertion. Written as x.(T), this pulls the concrete value of type T back out of an interface variable. The comma-ok form v, ok := x.(T) is safe. If the underlying type is not T, you get the zero value and ok == false instead of a program panic.
Type switch. Written as switch x := v.(type), this branches on the runtime type of an interface value. We use it in describe(v any) to handle int, string, and Task differently.
any. An alias for interface{}, the empty interface that every type satisfies. A function that accepts any accepts any value at all. You give up compile-time type safety and have to assert the type back out at runtime.
The map[int]Task store from Chapter 10 works fine as a local variable. The problem arises when other code needs to talk to it. If runStore takes a concrete *InMemoryStore, you can never pass it a *PostgresStore without changing the signature of runStore. Every caller holding an *InMemoryStore would need to be rewritten when the storage changes.
An interface solves this by inverting the dependency. Callers no longer know about InMemoryStore. They only know about TaskStore. The concrete type can change freely behind that boundary. In Chapter 23, you will see handlers and a service layer depending on this exact interface. In Chapters 29 and 30, a PostgresStore steps in and the handler code stays untouched.
There is a second payoff right now. LoggingStore wraps any TaskStore and logs every call before delegating it. Because it satisfies the same interface, it is transparent to runStore. You can stack store implementations without touching the code that uses them.
TaskStore interfacego// store.gotype TaskStore interface {Add(t Task) TaskGet(id int) (Task, bool)All() []Task}
Three methods. That is the entire contract. Any type that has these three methods with these exact signatures satisfies TaskStore. Nothing else is required.
Notice what is absent. There is no annotation on InMemoryStore or LoggingStore saying they satisfy TaskStore. Go verifies this automatically when you assign or pass a value to a variable of interface type. If the methods are wrong, the compiler tells you at that point, rather than at the type definition.
go// store.gotype InMemoryStore struct {tasks map[int]TasknextID int}func NewInMemoryStore() *InMemoryStore {return &InMemoryStore{tasks: make(map[int]Task)}}func (s *InMemoryStore) Add(t Task) Task {s.nextID++t.ID = s.nextIDs.tasks[t.ID] = treturn t}func (s *InMemoryStore) Get(id int) (Task, bool) {t, ok := s.tasks[id]return t, ok}func (s *InMemoryStore) All() []Task {out := make([]Task, 0, len(s.tasks))for _, t := range s.tasks {out = append(out, t)}sort.Slice(out, func(i, j int) bool { return out[i].ID < out[j].ID })return out}
The constructor NewInMemoryStore returns *InMemoryStore, which is the concrete type, not TaskStore. This follows a common Go idiom: accept interfaces, return structs. Callers who need flexibility ask for the interface. Constructors return the concrete type so callers who need the full API can use it.
All() sorts the results by ID before returning. Map iteration order in Go is unspecified, as we saw in Chapter 10. Without sorting, the output would differ between runs.
go// store.gotype LoggingStore struct {inner TaskStoreout io.Writer}func NewLoggingStore(inner TaskStore, out io.Writer) *LoggingStore {return &LoggingStore{inner: inner, out: out}}func (s *LoggingStore) Add(t Task) Task {_, _ = fmt.Fprintf(s.out, " [log] Add(%q)\n", t.Title)return s.inner.Add(t)}func (s *LoggingStore) Get(id int) (Task, bool) {_, _ = fmt.Fprintf(s.out, " [log] Get(%d)\n", id)return s.inner.Get(id)}func (s *LoggingStore) All() []Task {_, _ = fmt.Fprintln(s.out, " [log] All()")return s.inner.All()}
LoggingStore holds a TaskStore inside its inner field, rather than an *InMemoryStore. This means it can wrap any store. It could even wrap another LoggingStore if you wanted nested logging. It satisfies TaskStore itself because it has Add, Get, and All with the right signatures.
The _, _ = in front of the fmt.Fprintf and fmt.Fprintln calls is a linter requirement. The errcheck tool flags any unchecked error return, even from a log write. Writes to io.Writer can fail due to a broken pipe or a full disk. For a demo log, we discard the error intentionally. Chapter 12 covers when you must check these errors.
The out io.Writer parameter is itself an interface. It is the standard library's io.Writer with a single Write([]byte) (int, error) method. The demo passes os.Stdout, but anything that writes somewhere works, including a file, a buffer, or a network connection.
runStore — the proof of swappabilitygo// store.gofunc runStore(s TaskStore) {s.Add(Task{Title: "write code"})s.Add(Task{Title: "ship it"})if t, ok := s.Get(1); ok {fmt.Println(" Get(1):", t)}if _, ok := s.Get(99); !ok {fmt.Println(" Get(99): not found")}fmt.Println(" All():")for _, t := range s.All() {fmt.Println(" ", t)}}
runStore takes a TaskStore, adds two tasks, retrieves one by ID, tries a missing ID, and lists everything. It is called twice in interfacesDemo. It runs once with an InMemoryStore and once with a LoggingStore wrapping an InMemoryStore. The function body never changes between the two calls.
fmt.Stringer on Taskgo// task.gofunc (t Task) String() string {return t.Summary()}
fmt.Stringer is an interface defined in the standard library:
gotype Stringer interface {String() string}
Any type with a String() string method satisfies it. When you pass such a value to fmt.Println, fmt.Printf with %v or %s, or any of the fmt.Print functions, Go calls your String() method instead of falling back to the default struct formatting. This is why the runStore output reads #1 "write code" [open]. The Task's Stringer controls the format.
interfacesDemo shows both tools:
go// interfaces.govar store TaskStore = memif im, ok := store.(*InMemoryStore); ok {fmt.Printf("type assertion: it really is an *InMemoryStore with %d tasks\n", len(im.All()))}
The variable store is of type TaskStore. The assertion store.(*InMemoryStore) pulls the concrete type back out. The comma-ok form is safe. If the underlying type is different, ok is false and the program continues.
The type switch in describe handles multiple types in one block:
go// interfaces.gofunc describe(v any) string {switch x := v.(type) {case int:return fmt.Sprintf("an int (%d)", x)case string:return fmt.Sprintf("a string (%q)", x)case Task:return "a Task: " + x.String()default:return fmt.Sprintf("some other type: %T", x)}}
Inside each case, x is already the concrete type: an int, a string, or a Task. The default branch uses %T to print the type name. Use type switches when you need to do genuinely different things based on the runtime type. Reach for any sparingly. Once a value enters any, the compiler can no longer help you.
bashmake run

The two blocks under -- Interfaces -- run the same runStore function. The InMemoryStore block has no extra output. The LoggingStore block interleaves [log] lines before each delegated call. The Get(1) and Get(99) results and the All() contents are identical in both blocks. It is the exact same code, just with a different store underneath.
There is one subtlety in the output. [log] All() appears after the All(): header line. This happens because runStore prints the header first with fmt.Println(" All():"), and then calls s.All(). That is the moment LoggingStore logs the call. The log and the data come from the same call, in that order.
Chapter 12 covers errors, Go's other central abstraction. You will see how error is itself just an interface with one method, and why that makes error handling composable.