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)
nums = [1, 2, 3]
nums.append(4)
nums[0] # 1
nums[1:3] # [2, 3]
len(nums) # 4
for n in nums: ...
nums := []int{1, 2, 3}
nums = append(nums, 4)
nums[0] // 1
nums[1:3] // [2, 3]
len(nums) // 4
for _, n := range nums { ... }
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)
scores = {"alice": 95, "bob": 87}
scores["carol"] = 92
val = scores.get("dave", 0)
del scores["bob"]
for k, v in scores.items(): ...
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")
}
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.
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})"
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
}
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
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.