A concrete type is the direct type used to hold values and provide behavior. In Go, concrete types can still support polymorphism: they may satisfy interfaces through their methods without being interface types themselves. This keeps implementations free to evolve while callers depend only on the behavior they need. section 1.5.2 section 10.6
Start with concrete types when there is one clear implementation. Introducing an interface too early adds indirection and maintenance work without solving a real problem. When distinct concrete types later need to serve the same caller, identify their shared behavior and define a small interface around that actual need. section 10.4 section 10.6
A concrete database handle with polymorphic drivers
*sql.DB is a concrete type, yet it works with different database drivers through sql/driver interfaces. Application code calls methods on *DB rather than managing individual connections or dealing with database-specific details. The handle manages connection pooling, chooses connections as needed, and can retry failures automatically. Because *DB is concrete, it can add methods without requiring every driver to implement a growing public interface. section 10.1 section 10.6
Figure 10.1 The sql package abstracts underlying databases with drivers. The sql package’s registry stores registered drivers, and *DB manages a connection pool.
The driver pattern separates the concrete *DB handle from database-specific drivers: *DB supplies shared pool and query behavior while drivers communicate with their databases. section 10.1
This design avoids several problems that arise when a broad, central type is made an interface:
- Drivers need not implement every database operation exposed by *DB. section 10.1
- New *DB methods can be introduced without breaking existing drivers or callers. section 10.1
- Optional driver capabilities can be detected internally with type assertions while the external API remains uniform. section 10.6
Implicit interfaces make this incremental approach practical. A concrete type does not declare that it implements an interface; it is usable wherever the interface is expected when it provides all required methods. If a required method is missing, the program does not compile. This decouples concrete implementations from the interfaces that consume their behavior. section 1.5.2 section 10.4
Discovering a shared behavior
Two Shortener implementations—link.Shortener and sqlite.Shortener—create a reason to introduce an interface for a REST API integration. The interface follows the shared behavior already demonstrated by both concrete types, rather than forcing either implementation to conform to an abstraction chosen in advance. section 10.4
Concrete-first design does not reject interfaces. It gives each interface a specific job: represent a behavior required by a consumer, not a speculative taxonomy of implementations. The result is simpler code until variation is real, with a focused seam for substitution once it is needed. section 10.4 section 10.6


