Go Module 6 – Interfaces and type assertions
Interfaces you will meet: Stringer to Handler
Implement error, fmt.Stringer and sort.Interface in Go, build a priority queue on container/heap, and write http.Handler types that httptest can call.
What you will learn
- Implement error and fmt.Stringer, and say which one fmt uses for a value that has both
- Order a collection through sort.Interface, and build a priority queue on container/heap
- Write an http.Handler type and an http.HandlerFunc, and call them through httptest
- Update and remove the items of a container/heap queue with heap.Fix and heap.Remove
- Use encoding.TextMarshaler so that JSON map keys and flags read and write a type as text
Before you start
What you’ll learn · Before you start
What you will learn
- Implement error and fmt.Stringer, and say which one fmt uses for a value that has both
- Order a collection through sort.Interface, and build a priority queue on container/heap
- Write an http.Handler type and an http.HandlerFunc, and call them through httptest
- Update and remove the items of a container/heap queue with heap.Fix and heap.Remove
- Use encoding.TextMarshaler so that JSON map keys and flags read and write a type as text
Before you start
On this page
Three methods, Len, Less and Swap, let sort.Sort order any collection of yours, and two more turn the same type into a priority queue that always hands out the most urgent of eight deliveries first. The standard library is built on small interfaces like these: you write the methods, and a package you did not write does the rest.
You will implement error and fmt.Stringer, order deliveries with sort.Interface and container/heap, and serve requests through http.Handler, the interfaces you will meet in nearly every Go program. Every program here was run with Go 1.27.1, and its output appears beneath it.
A few methods connect your type to the library
Each of these interfaces is a promise: give your type its methods, and the package on the right will call them.
| Interface | Methods | Who calls them |
|---|---|---|
| error | Error() string | everything that reports a failure, and fmt |
| fmt.Stringer | String() string | fmt, for %v and Println |
| sort.Interface | Len, Less, Swap | sort.Sort and sort.Stable |
| heap.Interface | sort.Interface, Push, Pop | container/heap |
| io.Reader, io.Writer | Read, Write | io.Copy, bufio, compression, files, networks |
| http.Handler | ServeHTTP | net/http’s server, for every request |
Here is a seat with a String method, an error type, and a type with both:
// print_methods.go: a String method, an Error method, and a type with both, printed by fmt.
package main
import (
"errors"
"fmt"
)
// Seat prints as coach and berth.
type Seat struct {
Coach string
Berth int
}
func (s Seat) String() string { return fmt.Sprintf("%s-%d", s.Coach, s.Berth) }
// BerthError is an error: its Error method satisfies the error interface.
type BerthError struct{ Seat Seat }
func (e BerthError) Error() string { return "berth " + e.Seat.String() + " is taken" }
// Fault has both methods.
type Fault struct{}
func (Fault) String() string { return "the String method" }
func (Fault) Error() string { return "the Error method" }
func main() {
s := Seat{Coach: "B2", Berth: 34}
fmt.Println(s, []Seat{s, {Coach: "S1", Berth: 7}})
var err error = BerthError{Seat: s}
fmt.Println(err)
var be BerthError
fmt.Println(errors.As(err, &be), be.Seat.Berth)
fmt.Println(Fault{})
} Output
B2-34 [B2-34 S1-7] berth B2-34 is taken true 34 the Error method
Recorded with Go 1.27.1 on macOS 26 arm64. To run it yourself: go run print_methods.go
A fmt.Stringer controls how fmt prints a value: the Seat printed as B2-34, inside a slice too. A type with an Error
method is an error, the predeclared interface of the specification, so BerthError went into an error variable and
errors.As found it again. Fault has both methods, and fmt printed the Error method, because fmt, as its
documentation says, checks for error before Stringer. Formatted output with fmt covers String
methods in depth.
Checkpoint
Check: error and fmt.Stringer
sort.Interface and heap.Interface: an order you supply
sort.Interface asks for three methods: Len, Less, which reports whether element i sorts before element j, and Swap. container/heap asks for those three plus Push and Pop, and keeps the smallest element, by your Less, at index 0. One type of eight deliveries does both:
// deliveries.go: eight deliveries sorted by sort.Sort, and the same eight in a container/heap queue.
package main
import (
"container/heap"
"fmt"
"sort"
)
// Delivery is an order and the minutes left before it is due.
type Delivery struct {
Order int
DueMins int
}
// ByDue implements sort.Interface and, with Push and Pop, heap.Interface.
type ByDue []Delivery
func (q ByDue) Len() int { return len(q) }
func (q ByDue) Less(i, j int) bool { return q[i].DueMins < q[j].DueMins }
func (q ByDue) Swap(i, j int) { q[i], q[j] = q[j], q[i] }
// Push and Pop are for package heap to call; they add and remove at the end.
func (q *ByDue) Push(x any) { *q = append(*q, x.(Delivery)) }
func (q *ByDue) Pop() any {
old := *q
last := old[len(old)-1]
*q = old[:len(old)-1]
return last
}
func dues(q ByDue) []int {
out := make([]int, len(q))
for i, d := range q {
out[i] = d.DueMins
}
return out
}
func main() {
orders := ByDue{{1041, 35}, {1042, 12}, {1043, 50}, {1044, 8}, {1045, 41}, {1046, 27}, {1047, 19}, {1048, 60}}
sorted := append(ByDue(nil), orders...)
sort.Sort(sorted)
fmt.Println("sorted: ", dues(sorted))
q := append(ByDue(nil), orders...)
heap.Init(&q)
fmt.Println("heap array: ", dues(q))
for range 3 {
d := heap.Pop(&q).(Delivery)
fmt.Printf("next: order %d, due in %d min\n", d.Order, d.DueMins)
}
fmt.Println("left in heap:", dues(q))
} Output
sorted: [8 12 19 27 35 41 50 60] heap array: [8 12 19 35 41 27 50 60] next: order 1044, due in 8 min next: order 1042, due in 12 min next: order 1047, due in 19 min left in heap: [27 35 50 60 41]
Recorded with Go 1.27.1 on macOS 26 arm64. To run it yourself: go run deliveries.go
sort.Sort put all eight in order. heap.Init only arranged them as a binary heap: A tree stored in an array, in which every element is at most its children, at positions 2i + 1 and 2i + 2, so the smallest is at index 0. container/heap keeps a slice in this order through its Less method.: in the
array [8 12 19 35 41 27 50 60] each element is at most its children at positions 2i + 1 and 2i + 2, which is enough
to find the minimum at once. Each heap.Pop removed the most urgent delivery and repaired the heap. Step through the
three pops on the same eight due times:
- being compared or looked at
- being moved or written
- in its final place
- the key, pivot or target
Pseudo-code
-
extractMin(): min ← a[0]; a[0] ← a[size − 1]; size ← size − 1 -
i ← 0 -
while i has a child smaller than a[i]: -
c ← the smaller child; swap a[i] and a[c]; i ← c -
return min
A min-heap of 8 keys. extractMin removes the root, the smallest key, and repairs the heap from the top.
When it finishes: Taken out: 8 12 19.
Try your own input
The figure took out 8, 12 and 19 and left [27 35 50 60 41], exactly what deliveries.go printed. Push and Pop on the
type itself only add and remove at the end of the slice; container/heap’s documentation says they are for the package
to call, and you call heap.Push and heap.Pop.
Key idea
sort and container/heap never see your data: they call Len, Less and Swap, so the order is whatever your Less says.
Checkpoint
Check: sort.Interface and heap.Interface
http.Handler: one method between your code and the server
An http.Handler is any type with ServeHTTP(w http.ResponseWriter, r *http.Request). The server calls it once per
request; httptest lets a program, or a test, make the same call without a network:
// handler.go: a Handler type with state, a HandlerFunc, and both called through httptest.
package main
import (
"fmt"
"net/http"
"net/http/httptest"
)
// Stock answers how many units of an item are left. It is an http.Handler: it has ServeHTTP.
type Stock struct{ units map[string]int }
func (s *Stock) ServeHTTP(w http.ResponseWriter, r *http.Request) {
item := r.URL.Query().Get("item")
n, ok := s.units[item]
if !ok {
http.Error(w, "unknown item "+item, http.StatusNotFound)
return
}
fmt.Fprintf(w, "%s: %d left\n", item, n)
}
func main() {
stock := &Stock{units: map[string]int{"atta-5kg": 14}}
health := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "ok") // an ordinary function, adapted into a Handler
})
for _, c := range []struct {
h http.Handler
path string
}{{stock, "/stock?item=atta-5kg"}, {stock, "/stock?item=ghee-1l"}, {health, "/healthz"}} {
rec := httptest.NewRecorder()
c.h.ServeHTTP(rec, httptest.NewRequest("GET", c.path, nil))
fmt.Printf("%-22s %d %q\n", c.path, rec.Code, rec.Body.String())
}
} Output
/stock?item=atta-5kg 200 "atta-5kg: 14 left\n" /stock?item=ghee-1l 404 "unknown item ghee-1l\n" /healthz 200 "ok\n"
Recorded with Go 1.27.1 on macOS 26 arm64. To run it yourself: go run handler.go
*Stock is a Handler because it has ServeHTTP, and it answered 200 for atta and 404 for ghee. The health check was an
ordinary function, which http.HandlerFunc(…) converted into a Handler: HandlerFunc is a function type whose
ServeHTTP calls the function. Step through the first request:
The drawing shows the whole scene; its 6 steps are listed below.
Every step as text
- A request, no network. main builds GET /stock?item=atta-5kg with httptest.NewRequest, and a ResponseRecorder that stands in for the connection's response writer.
- The whole contract. The server's only call into a handler is ServeHTTP with a ResponseWriter and the request. main makes the same call directly.
- The handler reads the request. Stock reads item from the query string and finds 14 units under atta-5kg in its map.
- It writes the response. fmt.Fprintf writes the body to the ResponseWriter. Stock never called WriteHeader, so the status is 200.
- main reads what was written. The recorder kept the status and the body, so main printed 200 and the text. A test checks the same two fields.
- An unknown item. For ghee-1l the lookup fails, and http.Error writes status 404 with the message, which is what the second line printed.
Two more tools build on these interfaces. A container/heap queue can also change or cancel an item in place, with heap.Fix and heap.Remove. And encoding.TextMarshaler lets a type write and read itself as text for JSON keys, flags and logs.
Checkpoint
Check: http.Handler and HandlerFunc
A delivery queue on container/heap, measured Pro: opens with a Pro, Premium or Ultimate pass
A real dispatch queue changes while it runs: deadlines move, orders are cancelled, and the most urgent one must always come out first. This part reproduces the two mistakes that break a heap, keeps one right through updates and cancellations, and counts what each operation costs.
In this part:
- The queue’s own Pop and Push are not the queue’s operations
- heap.Fix and heap.Remove need to know where an item is
- What each operation costs, counted
3 runnable examples (Go) · about 4 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
TextMarshaler: one pair of methods for JSON keys, flags and text Pro: opens with a Pro, Premium or Ultimate pass
encoding.TextMarshaler and TextUnmarshaler let a type write itself as text and read itself back, and encoding/json, flag and other packages look for them. This part makes a seat usable as a JSON map key and as a flag, and shows which method json picks when a type has several.
In this part:
- A struct key works in JSON once it can write itself as text
- The same methods serve flags and validation
- Which method json uses when a type has several
- Text methods become part of the type’s contract
2 runnable examples (Go) · about 4 minutes
Opening this part with your pass…Pro part. It opens with the same pass as the part above.
Practise
Practice asks about Stringer and error, the ordering interfaces and Handler, beginning with whichever your answers show is weakest:
Practice that adapts to you
Practise this lesson
8 questions about 5 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.
Interview questions
Warm-up (fresher): Which methods does sort.Interface ask for, and what must Less promise?
Len, the number of elements; Less(i, j), whether element i must sort before element j; and Swap(i, j). Less must describe a strict weak ordering, and the sort documentation gives two of its rules as examples: if Less(i, j) and Less(j, k) are both true, so is Less(i, k), and if both are false, Less(i, k) is false too. Two elements count as equal when Less is false both ways, so write Less with less than: with less than or equal, each of two equal elements would sort before the other, which no strict weak ordering allows.
Three longer questions of the module bank build on this lesson:
-
Mid to senior
A dispatcher keeps deliveries in a container/heap queue. Sometimes a delivery due in 5 minutes goes out after ones due in 20. The code calls q.Push(d) when an order arrives and heap.Pop(&q) when a rider becomes available. Find the bug, fix it, and keep the queue right when a deadline changes or an order is cancelled.
-
Mid to senior
A new endpoint must return fares keyed by seat, where Seat is a struct with Coach and Berth, and json.Marshal of a map[Seat]Fare fails. What does encoding/json need from a map key type, how do you make Seat work as a key and as a query parameter, and what do you test?
Key takeaways
- error and fmt.Stringer are one method each; fmt prints with Error before String when a value has both.
- sort.Interface needs Len, Less and Swap, and heap.Interface adds Push and Pop for container/heap to call; the heap keeps the smallest element by Less at index 0.
- Any type with ServeHTTP is an http.Handler, HandlerFunc turns a function into one, and httptest calls handlers without a network.
Terms in this lesson
- binary heap
- A tree stored in an array, in which every element is at most its children, at positions 2i + 1 and 2i + 2, so the smallest is at index 0. container/heap keeps a slice in this order through its Less method.
References
- Package fmt: Stringer, and the order in which fmt checks methods (The Go Authors)
- The Go Programming Language Specification: Errors (The Go Authors)
- Package sort: Interface (The Go Authors)
- Package container/heap (The Go Authors)
- Package io: Reader and Writer (The Go Authors)
- Package net/http: Handler and HandlerFunc (The Go Authors)
- Package net/http/httptest (The Go Authors)
- Package encoding: TextMarshaler and TextUnmarshaler (The Go Authors)
- Package encoding/json: Marshal (map keys, Marshaler and TextMarshaler) (The Go Authors)
- Package flag: TextVar (The Go Authors)
- Package net/netip: Addr.UnmarshalText (The Go Authors)
- Package log/slog: TextHandler (The Go Authors)
- Go 1.27 Release Notes: encoding/json/v2 and encoding/json/jsontext (The Go Authors)
Report a problem with this lesson
Kept only in this browser. Your Learn progress