Section 07

Collections

Python has list, dict, set, tuple — all dynamic, all heterogeneous. Go has arrays (fixed size), slices (dynamic, backed by arrays), and maps. All homogeneous — one type per container.

Slices (Go's list)

Python
nums = [1, 2, 3]
nums.append(4)
nums[0]             # 1
nums[1:3]           # [2, 3]
len(nums)           # 4
for n in nums: ...
Go
nums := []int{1, 2, 3}
nums = append(nums, 4)
nums[0]             // 1
nums[1:3]           // [2, 3]
len(nums)           // 4
for _, n := range nums { ... }
Slices Share Underlying Arrays

b := a[1:3] does not copy. Both a and b point to the same memory. Mutating b[0] changes a[1]. To get an independent copy, use b := make([]int, len(a)) and copy(b, a), or in Go 1.21+: b := slices.Clone(a).

Maps (Go's dict)

Python
scores = {"alice": 95, "bob": 87}
scores["carol"] = 92
val = scores.get("dave", 0)
del scores["bob"]
for k, v in scores.items(): ...
Go
scores := map[string]int{"alice": 95, "bob": 87}
scores["carol"] = 92
val, ok := scores["dave"] // ok=false, val=0
delete(scores, "bob")
for k, v := range scores { ... }

The comma-ok idiom (val, ok := m[key]) is Go's equivalent of Python's .get(). If the key doesn't exist, ok is false and val is the zero value. Map iteration order is randomized by design — never rely on it.

No Set Type

// Idiomatic "set" in Go — map with empty struct values
seen := make(map[string]struct{})
seen["alice"] = struct{}{}

if _, exists := seen["alice"]; exists {
    fmt.Println("found")
}
Section 08

Structs & Methods

Go has no classes. It has structs (data) and methods (functions with a receiver). Data and behavior are separate — defined in the same package but not nested inside a class body.

Python
class Sensor:
    def __init__(self, id: str, rssi: float):
        self.id = id
        self.rssi = rssi

    def is_active(self) -> bool:
        return self.rssi > -80.0

    def __str__(self):
        return f"Sensor({self.id})"
Go
type Sensor struct {
    ID   string
    RSSI float64
}

func (s Sensor) IsActive() bool {
    return s.RSSI > -80.0
}

func (s Sensor) String() string {
    return fmt.Sprintf("Sensor(%s)", s.ID)
}

Value vs Pointer Receivers

// Value receiver — operates on a copy (like passing self by value)
func (s Sensor) IsActive() bool {
    return s.RSSI > -80.0
}

// Pointer receiver — can modify the struct (like Python's self)
func (s *Sensor) UpdateRSSI(rssi float64) {
    s.RSSI = rssi  // modifies the original
}
Receiver Rule of Thumb

Use a pointer receiver (*T) if the method modifies the struct, or if the struct is large (avoids copying). Use a value receiver (T) for small, read-only methods. Be consistent — if one method uses a pointer receiver, all methods on that type should.

Constructors

Go has no __init__. Convention: a NewXxx function.

func NewSensor(id string, rssi float64) *Sensor {
    return &Sensor{
        ID:   id,
        RSSI: rssi,
    }
}

s := NewSensor("sensor-001", -72.5)

Embedding (Go's "Inheritance")

type Base struct {
    ID        string
    CreatedAt time.Time
}

func (b Base) Age() time.Duration {
    return time.Since(b.CreatedAt)
}

type Sensor struct {
    Base             // embedded — Sensor "inherits" Base's fields and methods
    RSSI float64
}

s := Sensor{Base: Base{ID: "s1"}, RSSI: -70}
s.ID         // promoted from Base
s.Age()      // promoted from Base
Composition, Not Inheritance

Embedding is not inheritance — there is no super(), no method resolution order, no polymorphism through the embedded type. It's syntactic sugar for composition with field promotion. Sensor "has a" Base, not "is a" Base.