Last Updated: May 22, 2026
Go interfaces compose by embedding. Drop one interface name inside another, and the outer interface picks up every method of the inner one. That tiny rule is the reason the standard library is dominated by small, single-method interfaces that combine into bigger ones at the call site, like io.Reader and io.Writer joining into io.ReadWriter. This lesson covers the syntax, the method-set union semantics, the naming convention behind interfaces like io.ReadCloser, the case where two embedded interfaces share a method, and the practical guidance for designing interfaces this way.
The syntax is simple. Inside an interface body, you can write the name of another interface type instead of a method signature. That embedded interface contributes its full method set to the outer one.
PriceWithDiscount embeds two other interfaces. The compiler treats it as if you had written out both method signatures directly:
These two forms are interchangeable. Embedding is just a shorter way of spelling the same method set. The runtime doesn't track that PriceWithDiscount "extends" PriceQuoter or "inherits" from Discountable. There is no inheritance hierarchy. The composed interface is a flat method set, and a type satisfies it the moment its own methods cover every method in that flat set.
A concrete type sees no difference between the embedded form and the spelled-out form:
Book never names any of the three interfaces. It just defines Quote() and Discount() with the right signatures. The assignment var item PriceWithDiscount = Book{...} succeeds because Book's method set covers both required methods.
The composed interface is the union of its embedded interfaces' methods. Three things follow from that.
First, a type has to provide every method of every embedded interface to satisfy the composed one. Halfway counts as nothing. If Book had only Quote() and not Discount(), the assignment would fail with a compile error.
The compiler says something like RawProduct does not implement PriceWithDiscount (missing method Discount). The error names the specific missing method, which makes the fix obvious.
Second, the composed interface is still just an interface. It has no methods of its own beyond the union, no extra state, no behavior. A value typed as PriceWithDiscount can call Quote() or Discount(), but it can't call any method that wasn't contributed by an embedded interface.
Third, satisfaction works in both directions. If a type satisfies PriceWithDiscount, it automatically satisfies both PriceQuoter and Discountable on its own. So you can assign back to the smaller interfaces:
The reverse doesn't work. A PriceQuoter only promises a Quote() method, so you can't assign one to PriceWithDiscount without a type assertion that proves the underlying value also has Discount(). Type assertions are covered in the earlier chapter on that topic, and the same rules apply here.
The io package is the canonical example of this design. It defines a handful of single-method interfaces and then composes them into larger ones at the points where larger contracts are useful.
io.Reader and io.Writer are each one method. io.ReadWriter is their union. io.ReadCloser adds the Close() requirement to readers, which is what you want for a file or a network connection that owns a resource. io.ReadWriteCloser is all three. Every roll-up is built by embedding, not by re-declaring methods.
This design has a couple of practical effects worth understanding.
A function that only reads from a source takes io.Reader. It doesn't care whether the source can also write or close, only that it can read. That makes the function maximally reusable: it accepts files, network connections, byte buffers, gzip streams, anything with a Read method.
A function that needs both reading and writing takes io.ReadWriter. The interface name says exactly what's required, and the caller knows at a glance.
A function that has to close the resource when done takes io.ReadCloser. The name pairs the responsibility with the requirement.
strings.Reader (the type returned by strings.NewReader) has a Read method, so it satisfies io.Reader. readAndCount doesn't need anything else, so it asks for the smallest interface that covers its requirements. If we ever pass it a file or an HTTP response body, the same function works without changes, because both also satisfy io.Reader.
The naming convention is part of the pattern. Single-method interfaces are named after the method with -er on the end: Reader, Writer, Closer. Composed interfaces concatenate the names in a sensible reading order: ReadWriter, ReadCloser, ReadWriteCloser. When you see a name like WriteFlusher or ReadSeeker, you can guess the method set without looking it up.
Single methods compose well when the operations are independent. Payments are a good fit: a checkout flow needs to charge cards, refunds need to undo charges, and reporting needs to read transaction history. Each capability is a single method, and different processors support different combinations.
*CreditCard provides Charge, Refund, and History, so it satisfies Charger, Refunder, Reporter, and the composed FullProcessor. The function processCheckout asks for FullProcessor because it both charges and reads history, so the interface name encodes exactly what the function needs.
A gift card might only support charging, not refunding. That's fine: it satisfies Charger but not FullProcessor. Code that only charges still works with it, code that needs refunds doesn't accept it, and the compiler keeps the boundary honest.
Composing interfaces costs nothing at runtime. The composed type is still a single interface value (a two-word pair of type info plus data pointer), and method calls go through the same lookup as a non-composed interface. Embedding is purely a compile-time convenience.
The standard library's io design points to a broader rule: build small interfaces, compose at the use site. The opposite design, a single big interface with every method a caller might want, is harder to satisfy and harder to mock.
Compare two designs for the payments example. First, the monolithic version:
Every implementer has to provide all six methods, even ones it doesn't logically support. A mock for a unit test has to stub six methods to satisfy the interface, even if the test only calls one of them. Anyone reading a function signature func doThing(p PaymentService) has no idea which of the six methods the function actually uses.
Now the composed version:
Functions ask for only what they need. A checkout function takes a Charger. A reconciliation script takes a Charger plus Refunder plus Reporter, which you can either spell out as a parameter list or compose into an interface right at the call site:
That second form is an anonymous interface built inline, and it's about. The interface is defined right where it's used, has no name, and exists only as a parameter type. It's useful when the combination is one-off and naming it would just add clutter.
Mocks become trivial. A test that only exercises charging needs a type with a single Charge method. The compiler enforces the minimum.
An embedded interface doesn't have to live in the same package. You can embed any interface that's visible to you, including ones from the standard library.
NamedReader embeds io.Reader from the standard library and adds a local Name() method. Any type that has both a matching Read method and a Name() string method satisfies it. FileReader provides both, and the cross-package embedding works exactly like same-package embedding.
The output reads three bytes because the buffer happens to capture only the first chunk on a single Read call. That's how the io.Reader contract works: a single Read may return fewer bytes than the buffer holds, and the caller loops until it gets io.EOF. The detail is not central to composition, but it's for the io package in general.
A subtle point about embedding from other packages: the methods become part of the new interface's flat method set like any other embedded method. You don't get a Reader field or a Reader accessor on the composed interface. You get the Read method directly.
The interesting edge case is when two embedded interfaces declare the same method. Before Go 1.14, this was a compile error: the language considered any overlap a duplicate-method conflict, even when the signatures were identical. Since Go 1.14, the rule is more forgiving. If the duplicated method has identical signatures across all embedded interfaces, the composed interface includes that method once and the program compiles. If the signatures differ, the composition is still an error.
Here's the legal overlap:
Both Quoter and Pricer declare Quote() float64. The composed PriceLookup interface still has just one Quote method, because the duplicate has matching signatures. Book provides one Quote method and satisfies all three interfaces. This is the rule that lets you compose interfaces from different packages without worrying that a name collision will break the build, as long as the signatures actually agree.
Now the illegal version, where the signatures differ:
The compiler rejects this with an error like duplicate method Quote, because the two Quote methods have incompatible signatures. There's no value that could satisfy both at once, so the composed interface can't exist. The fix is to rename one of the methods, or to keep the two interfaces unrelated.
The Go 1.14 overlap rule is a compile-time relaxation only. There's no runtime change. If you maintain code that needs to support Go 1.13 or earlier (rare today), you may have to factor out the shared method into its own interface and embed that interface in both, which works on every version.
The relationships between the core io interfaces are easier to see as a diagram than as a wall of code. Each box is an interface, and the arrows show which interfaces are embedded into which.
The cyan nodes on the left are the three single-method building blocks. The orange nodes on the right are the composed roll-ups. Each composed interface is exactly the union of the methods of its embedded interfaces, no more and no less. ReadWriter is Read plus Write. ReadWriteCloser is all three. The diagram makes the "small interfaces compose into bigger ones" idea concrete: the building blocks stay small, and every useful combination is one line of code away.
The same pattern shows up in other parts of the standard library, like http.ResponseWriter, which is an interface that includes Header(), Write([]byte), and WriteHeader(int). That last one is not built by embedding (it's a flat three-method interface), which is worth noting. Composition is a tool, not a mandate. The net/http authors decided three closely related methods belonged in one named interface, and they wrote them out directly.
A few rules of thumb fall out of the patterns above.
Start with the smallest interface that captures one capability. One method, one purpose. The name should describe the verb (Charger, Refunder, Notifier). Use -er when natural.
Compose at the use site. When a function needs two or three capabilities, embed the smaller interfaces into a named or anonymous composed interface where the function is declared. Don't predefine giant composed interfaces "just in case".
Let real demand drive interface size. If three different functions all need the same trio of methods, that's a sign the composed interface deserves a name. If only one function needs the trio, the inline anonymous interface keeps the call site honest.
Avoid huge monolithic interfaces. A 10-method interface is usually wrong. The exception is when the methods genuinely belong together as one concept and every implementer truly provides all of them, like sort.Interface (covered below).
Don't pre-design interfaces before you need them. This sounds obvious, but it's the most common mistake in early Go code. Define a struct, write its methods, and only add an interface when a second concrete type needs to plug into the same call site. Composition is then easy: pick the methods the call site actually uses, and compose an interface from them.
Small interfaces composed at the use site are the design Go optimizes for.
One famous interface in the standard library doesn't compose anything: sort.Interface.
Three methods, no embedding, all defined inline. It's worth understanding why this is the shape that won.
The three methods only make sense together. Len without Less and Swap is useless for sorting. Less without Len doesn't tell you the range to iterate. Every sortable collection has to provide all three, and there's no value in being able to compose half of them. So the interface stays flat, three methods, no roll-up, and sort.Sort accepts it directly.
The lesson: compose when the methods are independently useful, keep flat when they only make sense together. io.Reader and io.Writer are independently useful; lots of code only reads or only writes. The three methods of sort.Interface are not independently useful; nothing wants Len alone. The right interface shape depends on whether the methods serve real, distinct purposes.
A flat three-method interface and a composed three-method interface are identical at runtime. The choice is purely about clarity at the source level. Don't compose for the sake of it; compose when the smaller pieces are useful in their own right.
Go 1.18 added generics with type constraints, and constraints look superficially like interfaces because they reuse the interface keyword. A constraint can include method requirements and even embed other interfaces, the same syntax you've seen in this lesson. Constraints can also do more than regular interfaces (they support type sets like ~int | ~float64), and they exist mainly to describe what type parameters are allowed.
For the purpose of this lesson, the only thing to remember is that the embedding syntax in a generic constraint behaves exactly like embedding in a regular interface: the constraint's method set is the union of the embedded interfaces' methods. The full story is in the Generics section. Composition is the same idea in both places.