1. Functions
Basic Definition
// Simple function
func add(a int, b int) int {
return a + b
}
// Same-type params can be grouped
func multiply(a, b float64) float64 {
return a * b
}
Multiple Return Values
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("division by zero")
}
return a / b, nil
}
result, err := divide(10, 3)
if err != nil {
log.Fatal(err)
}
Named Return Values
func swap(a, b string) (first, second string) {
first = b
second = a
return // naked return, returns named values
}
Variadic Parameters
func sum(nums ...int) int {
total := 0
for _, n := range nums {
total += n
}
return total
}
fmt.Println(sum(1, 2, 3)) // 6
fmt.Println(sum([]int{4, 5}...)) // 9, spread a slice
First-Class Functions & Closures
// Functions are first-class values
var op func(int, int) int = add
// Anonymous function
square := func(x int) int { return x * x }
fmt.Println(square(5)) // 25
// Closure: captures outer variable
func counter() func() int {
count := 0
return func() int {
count++
return count
}
}
c := counter()
fmt.Println(c()) // 1
fmt.Println(c()) // 2
// Higher-order function
func apply(nums []int, fn func(int) int) []int {
result := make([]int, len(nums))
for i, n := range nums {
result[i] = fn(n)
}
return result
}
doubled := apply([]int{1, 2, 3}, func(n int) int { return n * 2 })
// [2 4 6]
2. Structs
Definition & Fields
type User struct {
ID int
Name string
Email string
IsActive bool
}
// Create instances
u1 := User{ID: 1, Name: "Alice", Email: "alice@example.com", IsActive: true}
u2 := User{Name: "Bob"} // Other fields get zero values
// Access fields
fmt.Println(u1.Name) // "Alice"
u1.Email = "alice@new.com"
Constructor Convention (NewXxx)
func NewUser(name, email string) *User {
return &User{
ID: generateID(),
Name: name,
Email: email,
IsActive: true,
}
}
user := NewUser("Alice", "alice@example.com")
Struct Embedding (Composition over Inheritance)
type Address struct {
City string
Country string
}
type Employee struct {
User // Embedded struct (promoted fields)
Address // Another embedded struct
Department string
}
emp := Employee{
User: User{Name: "Alice"},
Address: Address{City: "Tokyo", Country: "Japan"},
Department: "Engineering",
}
// Access promoted fields directly
fmt.Println(emp.Name) // "Alice" (from User)
fmt.Println(emp.City) // "Tokyo" (from Address)
Struct Tags
type Product struct {
ID int `json:"id" db:"product_id"`
Name string `json:"name" validate:"required"`
Price float64 `json:"price,omitempty"`
}
// Tags are used by encoding/json, ORMs, validators, etc.
π Go has no classes or inheritance
Go uses struct + methods + interfaces instead of class hierarchies. Composition (embedding) replaces inheritance. This leads to simpler, more flexible designs.
3. Methods
type Rect struct {
Width, Height float64
}
// Value receiver: operates on a copy
func (r Rect) Area() float64 {
return r.Width * r.Height
}
// Pointer receiver: can modify the original
func (r *Rect) Scale(factor float64) {
r.Width *= factor
r.Height *= factor
}
func main() {
rect := Rect{Width: 10, Height: 5}
fmt.Println(rect.Area()) // 50
rect.Scale(2)
fmt.Println(rect.Area()) // 200
}
π‘ When to use pointer receiver?
- When the method needs to modify the receiver
- When the struct is large (avoids copying)
- For consistency β if one method uses a pointer receiver, all should
4. Interfaces
Implicit Implementation
// Define an interface
type Shape interface {
Area() float64
Perimeter() float64
}
type Circle struct {
Radius float64
}
// Circle implements Shape implicitly β no "implements" keyword
func (c Circle) Area() float64 {
return math.Pi * c.Radius * c.Radius
}
func (c Circle) Perimeter() float64 {
return 2 * math.Pi * c.Radius
}
// Use the interface
func printShape(s Shape) {
fmt.Printf("Area: %.2f, Perimeter: %.2f\n", s.Area(), s.Perimeter())
}
printShape(Circle{Radius: 5}) // Works!
Empty Interface & any
// any is an alias for interface{} (since Go 1.18)
func printAnything(v any) {
fmt.Printf("Type: %T, Value: %v\n", v, v)
}
printAnything(42)
printAnything("hello")
printAnything([]int{1, 2, 3})
Type Assertion & Type Switch
var val any = "hello"
// Type assertion
s, ok := val.(string)
if ok {
fmt.Println("String:", s)
}
// Type switch
func describe(i any) string {
switch v := i.(type) {
case int:
return fmt.Sprintf("integer: %d", v)
case string:
return fmt.Sprintf("string: %q", v)
case bool:
return fmt.Sprintf("boolean: %t", v)
default:
return fmt.Sprintf("unknown: %T", v)
}
}
Common Interfaces: io.Reader & io.Writer
// io.Reader and io.Writer are the most important interfaces in Go
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
// Files, HTTP bodies, buffers, network connections all implement these.
// This is the power of Go interfaces: write once, works with everything.
π‘ Go interfaces are implicitly satisfied. A type implements an interface simply by having all required methods β no declaration needed. This enables powerful decoupling.
5. Generics (Go 1.18+)
Generic Functions
// Type parameter with constraint
func Min[T int | float64 | string](a, b T) T {
if a < b {
return a
}
return b
}
fmt.Println(Min(3, 7)) // 3
fmt.Println(Min(3.14, 2.71)) // 2.71
fmt.Println(Min("apple", "banana")) // "apple"
Constraints
import "golang.org/x/exp/constraints"
// Using built-in constraint interface
func Sum[T constraints.Integer | constraints.Float](nums []T) T {
var total T
for _, n := range nums {
total += n
}
return total
}
// Custom constraint
type Number interface {
int | int32 | int64 | float32 | float64
}
func Double[T Number](n T) T {
return n * 2
}
Generic Structs
type Stack[T any] struct {
items []T
}
func (s *Stack[T]) Push(item T) {
s.items = append(s.items, item)
}
func (s *Stack[T]) Pop() (T, bool) {
if len(s.items) == 0 {
var zero T
return zero, false
}
item := s.items[len(s.items)-1]
s.items = s.items[:len(s.items)-1]
return item, true
}
// Usage
intStack := &Stack[int]{}
intStack.Push(1)
intStack.Push(2)
val, _ := intStack.Pop() // 2
π Chapter Summary
Multiple Returns
Functions can return multiple values. The (result, error) pattern is idiomatic Go.
Composition > Inheritance
Struct embedding promotes fields and methods. No class hierarchies.
Pointer vs Value Receiver
Use pointer receivers to mutate or for large structs. Value receivers for read-only operations.
Implicit Interfaces
Types satisfy interfaces automatically. No implements keyword. Enables loose coupling.
io.Reader/Writer
The most common interfaces. Files, HTTP, buffers β all share these interfaces.
Generics
Type parameters with constraints. Available since Go 1.18 for type-safe generic code.