
Go Interfaces
- 919 installs
- 137 repo stars
- Updated June 20, 2026
- cxuu/golang-skills
go-interfaces is an agent skill that guides developers on defining, implementing, and composing Go interfaces following Effective Go and Google and Uber style conventions.
About
go-interfaces is an Apache-2.0 cxuu/golang-skills module sourced from Effective Go, the Google Style Guide, and the Uber Style Guide for interface design and composition in Go. The skill applies when defining interfaces, choosing accept-interface versus return-concrete-type boundaries, writing type assertions with the comma-ok idiom, using type switches, or embedding types in public APIs—excluding generics-based polymorphism covered by go-generics. It ships bash scripts/check-interface-compliance.sh to find exported interfaces missing compile-time var _ I = (*T)(nil) assertions, plus references/EMBEDDING.md and references/RECEIVER-TYPE.md for deeper patterns. Core rules: consumers define interfaces, producers return concrete types, avoid embedding in public structs, prefer pointer receivers when any method mutates state, and add blank-identifier checks only when static conversions will not catch drift. Reach for go-interfaces when designing mockable Go package boundaries or reviewing whether an interface is premature.
- Accept Interfaces, Return Concrete Types pattern explained with concrete examples
- Scripts/check-interface-compliance.sh finds exported interfaces missing compile-time checks
- Guidance on type assertions, type switches, embedding, and mockable boundaries
- Decisions on when to accept an interface versus return a concrete type
- Sourced from Effective Go, Google Style Guide, and Uber Style Guide
Go Interfaces by the numbers
- 919 all-time installs (skills.sh)
- +39 installs in the week ending Aug 5, 2026 (Skillselion tracking)
- Ranked #431 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Security screen: LOW risk (skills.sh audit)
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/cxuu/golang-skills --skill go-interfacesAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 919 |
|---|---|
| repo stars | ★ 137 |
| Security audit | 3 / 3 scanners passed |
| Last updated | June 20, 2026 |
| Repository | cxuu/golang-skills ↗ |
When should Go code accept an interface versus a concrete type?
Get expert guidance on defining, implementing, and composing Go interfaces while following community best practices.
Who is it for?
Go backend developers designing testable package APIs who want Uber and Google style guidance on interfaces, embedding, and receiver choices.
Skip if: Generics-heavy polymorphism tasks covered by go-generics or projects with no interface abstraction needs.
When should I use this skill?
The user defines Go interfaces, debates interface versus concrete parameters, needs mockable test boundaries, or runs interface compliance checks.
What you get
Interface definitions at consumption sites, concrete constructor returns, compile-time satisfaction checks, and compliance script results.
- Interface definitions
- Compile-time satisfaction checks
- Compliance script output
By the numbers
- Bundles scripts/check-interface-compliance.sh and check-interface-compliance.go
- References 2 companion docs: EMBEDDING.md and RECEIVER-TYPE.md
- Cites 3 style sources: Effective Go, Google Style Guide, Uber Style Guide
Files
Go Interfaces and Composition
Available Scripts
- `scripts/check-interface-compliance.sh` — Finds exported interfaces missing compile-time compliance checks (
var _ I = (*T)(nil)). Runbash scripts/check-interface-compliance.sh --helpfor options.
---
Accept Interfaces, Return Concrete Types
Interfaces belong in the package that consumes values, not the package that implements them. Return concrete (usually pointer or struct) types from constructors so new methods can be added without refactoring.
// Good: consumer defines the interface it needs
package consumer
type Thinger interface { Thing() bool }
func Foo(t Thinger) string { ... }// Good: producer returns concrete type
package producer
type Thinger struct{ ... }
func (t Thinger) Thing() bool { ... }
func NewThinger() Thinger { return Thinger{ ... } }// Bad: producer defines and returns its own interface
package producer
type Thinger interface { Thing() bool }
type defaultThinger struct{ ... }
func NewThinger() Thinger { return defaultThinger{ ... } }Do not define interfaces before they are used. Without a realistic example of usage, it is too difficult to see whether an interface is even necessary.
---
Generality: Hide Implementation, Expose Interface
If a type exists only to implement an interface with no exported methods beyond that interface, return the interface from constructors to hide the implementation:
func NewHash() hash.Hash32 {
return &myHash{} // unexported type
}Benefits: implementation can change without affecting callers, substituting algorithms requires only changing the constructor call.
---
Type Assertions: Comma-Ok Idiom
Without checking, a failed assertion causes a runtime panic. Always use the comma-ok idiom to test safely:
str, ok := value.(string)
if ok {
fmt.Printf("string value is: %q\n", str)
}To check if a value implements an interface:
if _, ok := val.(json.Marshaler); ok {
fmt.Printf("value %v implements json.Marshaler\n", val)
}---
Type Switch
It's idiomatic to reuse the variable name (t := t.(type)) — the variable has the correct type in each case branch. When a case lists multiple types (case int, int64:), the variable has the interface type.
---
Embedding
Avoid embedding types in public structs — the inner type's full method set becomes part of your public API. Use unexported fields instead.
Read references/EMBEDDING.md when using struct embedding for composition, overriding embedded methods, resolving name conflicts, applying the HandlerFunc adapter pattern, or deciding whether to embed in public API types.
---
Interface Satisfaction Checks
Use a blank identifier assignment to verify a type implements an interface at compile time:
var _ json.Marshaler = (*RawMessage)(nil)This causes a compile error if *RawMessage doesn't implement json.Marshaler.
Use this pattern when:
- There are no static conversions that would verify the interface automatically
- The type must satisfy an interface for correct behavior (e.g., custom JSON
marshaling)
- Interface changes should break compilation, not silently degrade
Don't add these checks for every interface — only when no other static conversion would catch the error.
Validation: After defining interfaces or implementations, runbash scripts/check-interface-compliance.shto verify all concrete types have compile-timevar _ I = (*T)(nil)checks.
---
Receiver Type
If in doubt, use a pointer receiver. Don't mix receiver types on a single type — if any method needs a pointer, use pointers for all methods. Use value receivers only for small, immutable types (Point, time.Time) or basic types.
Read references/RECEIVER-TYPE.md when deciding between pointer and value receivers for a new type, especially for types with sync primitives or large structs.
---
Quick Reference
| Concept | Pattern | Notes |
|---|---|---|
| Consumer owns interface | Define interfaces where used | Not in the implementing package |
| Safe type assertion | v, ok := x.(Type) | Returns zero value + false |
| Type switch | switch v := x.(type) | Variable has correct type per case |
| Interface embedding | type RW interface { Reader; Writer } | Union of methods |
| Struct embedding | type S struct { *T } | Promotes T's methods |
| Interface check | var _ I = (*T)(nil) | Compile-time verification |
| Generality | Return interface from constructor | Hide implementation |
---
Related Skills
- Interface naming: See go-naming when naming interfaces (the
-ersuffix convention) or choosing receiver names - Error types: See go-error-handling when implementing the
errorinterface, custom error types, orerrors.Asmatching - Generics vs interfaces: See go-generics when deciding whether generics are needed or an interface already suffices
- Functional options: See go-functional-options when using an interface-based Option pattern for flexible constructors
- Compile-time checks: See go-defensive when adding
var _ I = (*T)(nil)satisfaction checks at API boundaries
Embedding Patterns in Go
Sources: Effective Go, Uber Style Guide
Go uses embedding for composition instead of inheritance. Embedding promotes the inner type's methods to the outer type, satisfying interfaces automatically.
Interface Embedding
Combine interfaces by embedding them:
type ReadWriter interface {
Reader
Writer
}A ReadWriter can do what a Reader does and what a Writer does. Only interfaces can be embedded within interfaces.
Struct Embedding
Embedding promotes methods from the inner type to the outer type without explicit forwarding.
type ReadWriter struct {
*Reader // *bufio.Reader
*Writer // *bufio.Writer
}With embedding, bufio.ReadWriter satisfies io.Reader, io.Writer, and io.ReadWriter automatically.
Mix embedded and named fields:
type Job struct {
Command string
*log.Logger
}
job.Println("starting now...")
job.Logger.SetPrefix("Job: ")Method Overriding
Define a method on the outer type to override the promoted method:
func (job *Job) Printf(format string, args ...any) {
job.Logger.Printf("%q: %s", job.Command, fmt.Sprintf(format, args...))
}The outer method takes precedence — calls to job.Printf(...) invoke the outer method, while the embedded method is still accessible via job.Logger.Printf(...).
Embedding vs Subclassing
When an embedded method is invoked, the receiver is the inner type, not the outer one. The embedded type has no knowledge that it is embedded — there is no equivalent to this or super referencing the containing type.
type Base struct{}
func (b *Base) Name() string { return "Base" }
type Derived struct{ Base }
d := Derived{}
d.Name() // returns "Base", not "Derived"Name Conflict Resolution
1. Outer hides inner — Fields or methods on the outer type shadow those promoted from an embedded type at the same name 2. Same-level conflicts are errors — If two embedded types at the same depth promote the same name, it is a compile error (unless the name is never accessed)
type A struct{}
func (A) Hello() string { return "A" }
type B struct{}
func (B) Hello() string { return "B" }
type C struct {
A
B
}
// c.Hello() // compile error: ambiguous selector
c.A.Hello() // OK: explicit disambiguationDon't Embed in Public Structs
Embedding exposes the inner type's full method set as part of your public API. This creates a maintenance burden: changes to the embedded type's methods break your API's compatibility guarantees.
Bad
type SMap struct {
sync.Mutex // Lock and Unlock are now part of SMap's API
data map[string]string
}Good
type SMap struct {
mu sync.Mutex // unexported field — implementation detail
data map[string]string
}
func (m *SMap) Get(k string) string {
m.mu.Lock()
defer m.mu.Unlock()
return m.data[k]
}Exception: Embedding is acceptable in test types and internal structs where API stability is not a concern.
The HandlerFunc Adapter Pattern
Methods can be defined on any named type, not just structs. The http.HandlerFunc pattern converts an ordinary function into an interface implementation:
type HandlerFunc func(ResponseWriter, *Request)
func (f HandlerFunc) ServeHTTP(w ResponseWriter, req *Request) {
f(w, req)
}Any function with the right signature becomes an HTTP handler:
http.Handle("/args", http.HandlerFunc(ArgServer))This adapter pattern is useful whenever you need a single-method interface satisfied by a standalone function.
Receiver Type: Pointer vs Value
Advisory: Go Wiki CodeReviewComments
Choosing whether to use a value or pointer receiver on methods can be difficult. If in doubt, use a pointer, but there are times when a value receiver makes sense.
When to Use Pointer Receiver
- Method mutates receiver: The receiver must be a pointer
- Receiver contains sync.Mutex or similar: Must be a pointer to avoid copying
- Large struct or array: A pointer receiver is more efficient. If passing all
elements as arguments feels too large, it's too large for a value receiver
- Concurrent or called methods might mutate: If changes must be visible in
the original receiver, it must be a pointer
- Elements are pointers to something mutating: Prefer pointer receiver to
make the intention clearer
When to Use Value Receiver
- Small unchanging structs or basic types: Value receiver for efficiency
- Map, func, or chan: Don't use a pointer to them
- Slice without reslicing/reallocating: Don't use a pointer if the method
doesn't reslice or reallocate the slice
- Small value types with no mutable fields: Types like
time.Timewith no
mutable fields and no pointers work well as value receivers
- Simple basic types:
int,string, etc.
// Value receiver: small, immutable type
type Point struct {
X, Y float64
}
func (p Point) Distance(q Point) float64 {
return math.Hypot(q.X-p.X, q.Y-p.Y)
}
// Pointer receiver: method mutates receiver
func (p *Point) ScaleBy(factor float64) {
p.X *= factor
p.Y *= factor
}
// Pointer receiver: contains sync.Mutex
type Counter struct {
mu sync.Mutex
count int
}
func (c *Counter) Increment() {
c.mu.Lock()
c.count++
c.mu.Unlock()
}Consistency Rule
Don't mix receiver types. Choose either pointers or struct types for all available methods on a type. If any method needs a pointer receiver, use pointer receivers for all methods.
// Good: Consistent pointer receivers
type Buffer struct {
data []byte
}
func (b *Buffer) Write(p []byte) (int, error) { /* ... */ }
func (b *Buffer) Read(p []byte) (int, error) { /* ... */ }
func (b *Buffer) Len() int { return len(b.data) }
// Bad: Mixed receiver types
func (b Buffer) Len() int { return len(b.data) } // inconsistent#!/usr/bin/env bash
set -euo pipefail
VERSION="1.0.0"
SCRIPT_NAME="$(basename "$0")"
usage() {
cat <<EOF
$SCRIPT_NAME v$VERSION — Check for missing compile-time interface compliance verifications
USAGE
bash $SCRIPT_NAME [options] [path]
DESCRIPTION
Scans Go files for exported interface definitions and checks whether each
has a corresponding compile-time compliance assertion like:
var _ MyInterface = (*MyImpl)(nil)
var _ MyInterface = MyImpl{}
Reports interfaces that lack such compile-time checks. This helps catch
interface drift at compile time instead of runtime.
Exits 0 if all interfaces are verified, 1 if missing checks found, 2 on error.
OPTIONS
-h, --help Show this help message
-v, --version Show version
--json Output results as JSON
--include-test Also scan _test.go files for compliance checks
--limit N Show at most N results (default: all)
ARGUMENTS
path Directory to scan (default: current directory)
EXAMPLES
bash $SCRIPT_NAME
bash $SCRIPT_NAME ./pkg/storage
bash $SCRIPT_NAME --json .
bash $SCRIPT_NAME --include-test ./internal
EOF
}
JSON_OUTPUT=false
INCLUDE_TEST=false
LIMIT=0
TARGET=""
while [[ $# -gt 0 ]]; do
case "$1" in
-h|--help) usage; exit 0 ;;
-v|--version) echo "$SCRIPT_NAME v$VERSION"; exit 0 ;;
--json) JSON_OUTPUT=true; shift ;;
--include-test) INCLUDE_TEST=true; shift ;;
--limit) LIMIT="${2:?error: --limit requires a number}"; shift 2 ;;
-*) echo "error: unknown option: $1" >&2; usage >&2; exit 2 ;;
*) TARGET="$1"; shift ;;
esac
done
TARGET="${TARGET:-.}"
if [[ ! -d "$TARGET" && ! -f "$TARGET" ]]; then
# Handle ./... patterns
dir="${TARGET%%/...}"
dir="${dir:-.}"
if [[ ! -d "$dir" ]]; then
echo "error: path not found: $TARGET" >&2
exit 2
fi
TARGET="$dir"
fi
json_escape() {
local s="$1"
s="${s//\\/\\\\}"
s="${s//\"/\\\"}"
s="${s//$'\t'/\\t}"
s="${s//$'\r'/}"
s="${s//$'\n'/\\n}"
printf '%s' "$s"
}
# Collect all Go source files
find_go_files() {
local t="$1"
if $INCLUDE_TEST; then
find "$t" -name '*.go' ! -path '*/vendor/*' ! -path '*/.git/*' 2>/dev/null
else
find "$t" -name '*.go' ! -name '*_test.go' ! -path '*/vendor/*' ! -path '*/.git/*' 2>/dev/null
fi
}
# Collect all Go files (including tests) for checking compliance vars
find_all_go_files() {
find "$1" -name '*.go' ! -path '*/vendor/*' ! -path '*/.git/*' 2>/dev/null
}
# Step 1: Find all exported interface definitions
IFACE_NAMES=()
IFACE_LOCATIONS=()
while IFS= read -r file; do
[[ -n "$file" ]] || continue
line_num=0
while IFS= read -r line; do
line_num=$((line_num + 1))
# Match: type ExportedName interface {
pat='^[[:space:]]*type[[:space:]]+([A-Z][a-zA-Z0-9]*)[[:space:]]+interface[[:space:]]*\{'
if [[ "$line" =~ $pat ]]; then
iface_name="${BASH_REMATCH[1]}"
IFACE_NAMES+=("$iface_name")
IFACE_LOCATIONS+=("$file:$line_num")
fi
done < "$file"
done < <(find_go_files "$TARGET")
if [[ ${#IFACE_NAMES[@]} -eq 0 ]]; then
if $JSON_OUTPUT; then
echo '{"interfaces":[],"missing":[],"count_interfaces":0,"count_missing":0}'
else
echo "No exported interfaces found in: $TARGET"
fi
exit 0
fi
# Step 2: Scan all Go files (including tests) for compliance checks
# Pattern: var _ InterfaceName = ...
ALL_GO_FILES=()
while IFS= read -r f; do
[[ -n "$f" ]] && ALL_GO_FILES+=("$f")
done < <(find_all_go_files "$TARGET")
MISSING=()
for ((i=0; i<${#IFACE_NAMES[@]}; i++)); do
iface_name="${IFACE_NAMES[$i]}"
location="${IFACE_LOCATIONS[$i]}"
# Look for: var _ InterfaceName = (various patterns)
if ! grep -qlE "var[[:space:]]+_[[:space:]]+${iface_name}[[:space:]]*=" \
"${ALL_GO_FILES[@]}" 2>/dev/null; then
MISSING+=("${iface_name}|${location}")
fi
done
# Sort for stable output
IFS=$'\n' MISSING=($(sort <<<"${MISSING[*]}")); unset IFS
# Truncation
TOTAL=${#MISSING[@]}
TRUNCATED=false
if [[ $LIMIT -gt 0 && $TOTAL -gt $LIMIT ]]; then
MISSING=("${MISSING[@]:0:$LIMIT}")
TRUNCATED=true
fi
# Output results
if $JSON_OUTPUT; then
echo "{"
echo ' "interfaces": ['
first=true
SORTED_INDICES=()
for ((i=0; i<${#IFACE_NAMES[@]}; i++)); do
SORTED_INDICES+=("$i|${IFACE_NAMES[$i]}")
done
IFS=$'\n' SORTED_INDICES=($(sort -t'|' -k2 <<<"${SORTED_INDICES[*]}")); unset IFS
for entry in "${SORTED_INDICES[@]}"; do
i="${entry%%|*}"
iface_name="${IFACE_NAMES[$i]}"
location="${IFACE_LOCATIONS[$i]}"
file="${location%%:*}"
line="${location#*:}"
$first || echo ","
first=false
printf ' {"name":"%s","file":"%s","line":%s}' "$(json_escape "$iface_name")" "$(json_escape "$file")" "$line"
done
echo ""
echo " ],"
echo ' "missing": ['
first=true
for entry in "${MISSING[@]+"${MISSING[@]}"}"; do
IFS='|' read -r name location <<< "$entry"
file="${location%%:*}"
line="${location#*:}"
$first || echo ","
first=false
printf ' {"name":"%s","file":"%s","line":%s}' "$(json_escape "$name")" "$(json_escape "$file")" "$line"
done
echo ""
echo " ],"
printf ' "count_interfaces": %d,\n' "${#IFACE_NAMES[@]}"
printf ' "count_missing": %d,\n' "$TOTAL"
printf ' "truncated": %s\n' "$TRUNCATED"
echo "}"
else
echo "Exported interfaces found: ${#IFACE_NAMES[@]}"
echo ""
if [[ $TOTAL -eq 0 ]]; then
echo "All interfaces have compile-time compliance checks."
exit 0
fi
echo "Missing compile-time compliance checks:"
echo ""
for entry in "${MISSING[@]}"; do
IFS='|' read -r name location <<< "$entry"
printf " %s interface '%s' has no 'var _ %s = ...' assertion\n" "$location" "$name" "$name"
done
if $TRUNCATED; then
echo " ... and $((TOTAL - LIMIT)) more (use --limit to adjust)"
fi
echo ""
echo "Add compile-time checks like:"
echo " var _ MyInterface = (*MyImpl)(nil)"
echo ""
echo "Total: $TOTAL interface(s) missing verification"
fi
if [[ $TOTAL -gt 0 ]]; then
exit 1
fi
exit 0
Related skills
How it compares
Pick go-interfaces for package-boundary interface design; use go-generics when polymorphism should use type parameters instead of interfaces.
FAQ
Where should Go interfaces be defined per go-interfaces?
go-interfaces follows accept interfaces, return concrete types—define interfaces in the consumer package that uses the behavior, and return structs or pointers from constructors in the implementing package.
What script validates Go interface compliance?
go-interfaces bundles scripts/check-interface-compliance.sh, a bash helper that flags exported interfaces missing compile-time var _ Interface = (*Concrete)(nil) assertions, backed by check-interface-compliance.go.
Does go-interfaces cover Go generics?
go-interfaces explicitly does not cover generics-based polymorphism; the skill points to the separate go-generics skill when type parameters replace interface abstractions.
Is Go Interfaces safe to install?
skills.sh reports 3 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.