Functions & Methods Basics
10 examples to get you started with Functions & Interfaces - 7 basic and 3 intermediate.
Prerequisites
- Go 1.26.x installed (
go version). - No third-party modules required - snippets use the standard library only.
Basic Examples
1. Named Function with Multiple Returns
Go functions return explicit values, often a result plus error.
package main
import (
"fmt"
"strconv"
)
func parsePort(s string) (int, error) {
return strconv.Atoi(s)
}
func main() {
port, err := parsePort("8080")
if err != nil {
fmt.Println("parse:", err)
return
}
fmt.Println("listening on", port)
}- Multiple return values replace out-parameters and exception side channels.
- Callers must handle
errorat the call site - control flow stays visible. - Zero values apply when you ignore a return with
_.
Related: Multiple Returns, Named Returns & Naked Returns - when to name results
2. Method with Value Receiver
Methods attach behavior to types; value receivers operate on a copy.
package main
import "fmt"
type Counter struct {
n int
}
func (c Counter) Value() int {
return c.n
}
func main() {
c := Counter{n: 3}
fmt.Println(c.Value())
}- Receiver
(c Counter)copies the struct for the method body. - Read-only accessors often use value receivers to avoid accidental mutation.
- Methods are just functions with a receiver parameter.
Related: Methods: Value vs Pointer Receivers - choosing receiver kind
3. Method with Pointer Receiver
Pointer receivers mutate the original value and match larger structs efficiently.
package main
import "fmt"
type Counter struct {
n int
}
func (c *Counter) Inc() {
c.n++
}
func main() {
c := &Counter{}
c.Inc()
c.Inc()
fmt.Println(c.n)
}(c *Counter)letsIncmodify caller state.- Go automatically takes the address when calling
c.Inc()on a value variable. - Consistency matters: mixing receiver kinds on one type confuses interface satisfaction.
4. Consumer-Side Interface
Interfaces are satisfied implicitly when methods match.
package main
import "fmt"
type Stringer interface {
String() string
}
type User struct {
Name string
}
func (u User) String() string {
return "user:" + u.Name
}
func printLabel(s Stringer) {
fmt.Println(s.String())
}
func main() {
printLabel(User{Name: "ada"})
}Usernever mentionsStringer- the compiler checks at assignment.- Define interfaces where they are consumed to keep dependencies narrow.
- Small interfaces make test doubles trivial.
Related: Defining and Implementing Interfaces - API design patterns
5. Function Type and Value
Functions are first-class values with their own named types.
package main
import "fmt"
type Op func(int, int) int
func apply(a, b int, op Op) int {
return op(a, b)
}
func main() {
add := func(x, y int) int { return x + y }
fmt.Println(apply(2, 3, add))
}type Op func(...)documents intent better than repeatingfunc(int,int) int.- Function values can close over variables (closures) - see intermediate examples.
- Higher-order functions replace some interface ceremony for single hooks.
Related: Variadic Functions & Function Types - variadic and HOF patterns
6. Variadic Parameter
The final parameter can absorb a variable argument list.
package main
import "fmt"
func sum(nums ...int) int {
total := 0
for _, n := range nums {
total += n
}
return total
}
func main() {
fmt.Println(sum(1, 2, 3, 4))
}nums ...intis a slice inside the function ([]int).- Callers may pass zero or many arguments; you can also spread a slice with
sum(vals...). - Variadic must be the last parameter in the signature.
7. Named Result Parameters
Result names act as variables in the function body and appear in documentation.
package main
import "fmt"
func divide(a, b float64) (quot float64, ok bool) {
if b == 0 {
return 0, false
}
quot = a / b
ok = true
return
}
func main() {
q, ok := divide(10, 2)
fmt.Println(q, ok)
}- Named results initialize to zero values at function entry.
- Bare
returnreturns the current named values - use sparingly for clarity. - Helpful in short defer-heavy functions; harmful when they obscure logic.
Related: Multiple Returns, Named Returns & Naked Returns - naked return trade-offs
Intermediate Examples
8. Closure Capturing Loop Variable (Safe Pattern)
Closures capture variables by reference; loop iterators need a local copy per iteration.
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
for i := 0; i < 3; i++ {
i := i
wg.Add(1)
go func() {
defer wg.Done()
fmt.Println(i)
}()
}
wg.Wait()
}- Without
i := i, goroutines may all observe the final loop value. defer wg.Done()pairs withAddfor clean shutdown of concurrent work.- Prefer passing loop vars as function parameters when the closure is reused.
Related: Function Literals & Closures - factory and middleware patterns
9. Accept Interface, Return Struct
Wire dependencies through small interfaces; constructors return concrete types.
package main
import (
"context"
"fmt"
)
type Notifier interface {
Notify(ctx context.Context, msg string) error
}
type Logger struct{}
func (Logger) Notify(ctx context.Context, msg string) error {
fmt.Println("notify:", msg)
return nil
}
type Service struct {
n Notifier
}
func NewService(n Notifier) *Service {
return &Service{n: n}
}
func (s *Service) Run(ctx context.Context) error {
return s.n.Notify(ctx, "ready")
}
func main() {
svc := NewService(Logger{})
_ = svc.Run(context.Background())
}NewServiceacceptsNotifierfor tests; returns*Serviceas concrete API.- Pass
context.Contextas the first parameter on I/O boundaries. - Keeps packages composable without inheritance or DI frameworks.
Related: Functions and Interfaces: Go's Composition Model - mental model for composition
10. Typed Nil in an Interface Variable
A nil pointer stored in an interface is not equal to an untyped nil interface.
package main
import "fmt"
type Worker interface {
Work() error
}
type job struct{}
func (j *job) Work() error { return nil }
func main() {
var w Worker = (*job)(nil)
fmt.Println(w == nil)
}wholds type*jobwith valuenil- the interface itself is non-nil.if w == nilchecks fail even though no concrete instance exists.- Return interfaces only when necessary; document "nil means absent" explicitly.
Related: Nil Interface vs Nil Pointer - API designs that avoid the trap
Stack versions: This page was written for Go 1.26.x (Green Tea GC default, go fix modernizers - verify patch at build), chi (latest - verify at build), gin (latest - verify at build), echo (latest - verify at build), google.golang.org/grpc (latest - verify at build), sigs.k8s.io/controller-runtime (latest - verify at build), kubebuilder (latest - verify at build), tinygo (latest - verify board targets at build), wazero (latest - verify at build), and golangci-lint (latest - verify linter set at build).