Go: Go Map and Structures
Last updated: 2026-08-26
Maps and structs are the two pillars of data modeling in Go—maps handle dynamic queries, while structs describe fixed structures—and together, they can describe 90% of business objects.
Maps provide O(1) key-value lookups, while structs aggregate data using fields. In this lesson, you'll master all the core concepts of data modeling in Go and be able to use them to build a complete e-commerce inventory system.
1. You will learn
- CRUD (Create/Read/Update/Delete) for maps
- comma-ok: Checks whether the key exists
- Iterating Over and Deleting Elements in a Map's Range
- Defining, initializing, and accessing fields in a struct
- struct علامة (key for JSON serialization)
- Nested Structures and Composition
- Choosing Between a Map and a Slice
- Building an E-commerce SKU Inventory System Using Maps and Structs
2. The True Story of an E-commerce Engineer
(1) Pain point: Slice lookups are too slow
Bob is a واجهة خلفية engineer at an e-commerce platform. With the Double 11 pre-sale campaign underway, the inventory query API has been incredibly slow:
"We have 100,000 SKUs in stock, stored using a
[]Productslice, and every query involves a linear scan. As QPS increases, the average استجابة time is 800 ms with 100% CPU usage. My boss has given me three days to optimize this to under 100 ms."
He opened the code he had written:
// Version 1: Store all products using a slice
var products []Product
func findProduct(sku string) *Product {
for _, p := range products { // O(n) linear scan
if p.SKU == sku {
return &p
}
}
return nil
}
100,000 products × 1,000 QPS = 100 million traversals per second; 100% CPU utilization is inevitable.
(2) Solution in Go: Use a map for O(1) lookups
// inventory.go
package main
import "fmt"
type Product struct {
SKU سلسلة // Product unique identifier
Name سلسلة // Product name
Price float64 // Price
Stock int // Stock count
Category سلسلة // Classification
}
// Using map to create a hash table
type Inventory struct {
products map[سلسلة]Product // SKU -> Product
}
func NewInventory() *Inventory {
return &Inventory{products: make(map[سلسلة]Product)}
}
// O(1) query
func (inv *Inventory) Find(sku سلسلة) (Product, bool) {
p, ok := inv.products[sku]
return p, ok
}
// O(1) update
func (inv *Inventory) UpdateStock(sku سلسلة, delta int) خطأ {
p, ok := inv.products[sku]
if !ok {
return fmt.Errorf("SKU %s not found", sku)
}
p.Stock += delta
inv.products[sku] = p
return nil
}
func main() {
inv := NewInventory()
// Bulk import of 100,000 SKUs
for i := 0; i < 100000; i++ {
sku := fmt.Sprintf("SKU-%05d", i)
inv.products[sku] = Product{
SKU: sku,
Name: fmt.Sprintf("Product-%d", i),
Price: 99.99,
Stock: 100,
Category: "electronics",
}
}
// Query performance comparison
p, ok := inv.Find("SKU-50000")
if ok {
fmt.Printf("Found: %s, price=%.2f\n", p.Name, p.Price)
}
// Update inventory
inv.UpdateStock("SKU-50000", -5)
}
Output:
Found: Product-50000, price=99.99
(3) Performance: Query Performance of slice vs. map
| Data Size | Linear Scan in slice | Hash Lookup in map | Performance Gap |
|---|---|---|---|
| 100 SKUs | 50 ns | 50 ns | equivalent |
| 1,000 SKUs | 500 ns | 50 ns | 10x |
| 10,000 SKUs | 5 µs | 50 ns | 100x |
| 100,000 SKUs | 50 µs | 50 ns | 1,000x |
3. Map Basics
(1) Three Ways to Create a Map
package main
import "fmt"
func main() {
// Method 1: make (Recommended)
m1 := make(map[string]int) // empty map, writable
// Method 2: make + pre-allocated capacity
m2 := make(map[string]int, 100) // pre-allocate 100 capacity, reduce resizing
// Method 3: Literal Initialization
m3 := map[string]int{
"Alice": 28,
"Bob": 32,
}
// Method 4: nil map (read-only, cannot be written to)
var m4 map[string]int // == nil, cannot do m4["a"] = 1
_ = m4
}
(2) map CRUD
package main
import "fmt"
func main() {
ages := make(map[سلسلة]int)
// Create
ages["Alice"] = 28
ages["Bob"] = 32
// Read
fmt.Println(ages["Alice"]) // 28
// Update
ages["Alice"] = 29
// Delete
delete(ages, "Bob")
// Length
fmt.Printf("len=%d\n", len(ages))
fmt.Println(ages) // map[Alice:29]
}
Output:
28
len=1
map[Alice:29]
▶ Example: map iterate (order is random)
package main
import "fmt"
func main() {
ages := map[string]int{"Alice": 28, "Bob": 32, "Charlie": 45}
// for range: key + value
for name, age := range ages {
fmt.Printf("%s is %d years old\n", name, age)
}
// Only the key
for name := range ages {
fmt.Printf("name: %s\n", name)
}
// Only the value (use _ to ignore the key)
for _, age := range ages {
fmt.Printf("age: %d\n", age)
}
}
Output (in random order):
Alice is 28 years old
Charlie is 45 years old
Bob is 32 years old
name: Bob
...
4. comma-ok Syntax (Key Point)
(1) Distinguish between "zero value" and "does not exist"
package main
import "fmt"
func main() {
ages := map[سلسلة]int{"Alice": 28}
// Incorrect approach: Cannot distinguish between "key does not exist" and "value = 0"
age := ages["Bob"]
fmt.Printf("Bob's age: %d\n", age) // 0 (but is Bob really 0 years old?)
// Correct approach: comma-ok
age, ok := ages["Bob"]
if ok {
fmt.Printf("Bob's age: %d\n", age)
} else {
fmt.Println("Bob not found")
}
// Just check if it exists: discard the value
_, exists := ages["Bob"]
fmt.Printf("Bob exists: %v\n", exists)
}
Output:
Bob's age: 0
Bob not found
Bob exists: false
(2) comma-ok in Practice: Caching Queries
package main
import "fmt"
// Cache: key → value
var cache = make(map[string]string)
func getCached(key string) (string, bool) {
val, ok := cache[key]
return val, ok
}
func main() {
// Set cache
cache["user:1"] = "Alice"
cache["user:2"] = "Bob"
// Query cache
if val, ok := getCached("user:1"); ok {
fmt.Printf("Hit: %s\n", val)
} else {
fmt.Println("Miss")
}
if _, ok := getCached("user:999"); !ok {
fmt.Println("user:999 not in cache, fetching from DB...")
}
}
Output:
Hit: Alice
user:999 not in cache, fetching from DB...
(3) comma-ok General Pattern
| Context | Syntax | Meaning |
|---|---|---|
| Map lookup | v, ok := m[key] |
Does the key exist? |
| Type Assertion | v, ok := x.(T) |
Type Match? |
| Receiving from channel | v, ok := <-ch |
Channel closed? |
5. Basics of Structs
(1) Defining and Initializing Structures
package main
import "fmt"
// define struct
type User struct {
Name سلسلة
Age int
City سلسلة
}
func main() {
// Method 1: By field order (not recommended; poor readability)
u1 := User{"Alice", 28, "Shanghai"}
// Method 2: Initializing Field Names (Recommended)
u2 := User{
Name: "Bob",
Age: 32,
City: "Beijing",
}
// Method 3: Partial Initialization (with the remainder set to zero)
u3 := User{Name: "Charlie"} // Age=0, City=""
// Method 4: new() returns a pointer
u4 := new(User)
u4.Name = "Dave"
fmt.Println(u1, u2, u3, u4)
}
Output:
{Alice 28 Shanghai} {Bob 32 Beijing} {Charlie } &{Dave 0 }
(2) Field Access and Modification
package main
import "fmt"
type User struct {
Name string
Age int
}
func main() {
u := User{Name: "Alice", Age: 28}
// Read field
fmt.Println(u.Name) // Alice
// Write to a field
u.Age = 29
// Pointer access (automatic dereferencing)
p := &u
fmt.Println(p.Name) // Alice (equivalent to (*p).Name)
p.Age = 30 // automatic dereferencing
}
▶ Example: Struct Copying vs. Pointers
package main
import "fmt"
type Counter struct {
Value int
}
// Pass-by-value: Copy the entire struct
func incrementByValue(c Counter) {
c.Value++ // modifies the copy
fmt.Printf("Inside دالة: %d\n", c.Value)
}
// Pointer passing: pass by reference
func incrementByPointer(c *Counter) {
c.Value++ // modifies the original
fmt.Printf("Inside دالة: %d\n", c.Value)
}
func main() {
c := Counter{Value: 10}
incrementByValue(c)
fmt.Printf("After value passing: %d\n", c.Value) // 10 (unchanged)
incrementByPointer(&c)
fmt.Printf("After pointer passing: %d\n", c.Value) // 11 (modified)
}
Output:
Inside function: 11
After value passing: 10
Inside function: 11
After pointer passing: 11
6. struct علامة (Key to JSON Serialization)
(1) struct علامة syntax
type User struct {
Name string `json:"name" db:"user_name"`
Age int `json:"age" validate:"min=0,max=150"`
Email string `json:"email,omitempty"`
Password string `json:"-"` // - means JSON ignores this field
}
(2) Hands-On JSON Serialization
package main
import (
"encoding/json"
"fmt"
)
type User struct {
Name سلسلة `json:"name"`
Age int `json:"age"`
Email سلسلة `json:"email,omitempty"`
Password سلسلة `json:"-"` // password not serialized
}
func main() {
u := User{
Name: "Alice",
Age: 28,
Email: "alice@example.com",
Password: "secret123",
}
// Serialization: struct → JSON
data, _ := json.Marshal(u)
fmt.Printf("JSON: %s\n", data)
// Deserialization: JSON → struct
jsonStr := `{"name":"Bob","age":32,"email":"bob@example.com"}`
var u2 User
json.Unmarshal([]byte(jsonStr), &u2)
fmt.Printf("Deserialization: %+v\n", u2)
}
Output:
JSON: {"name":"Alice","age":28,"email":"alice@example.com"}
Deserialization: {Name:Bob Age:32 Email:bob@example.com Password:}
(3) Commonly used struct علامة libraries
| Library | Tag Name | Purpose |
|---|---|---|
| encoding/json | json:"name" |
JSON field name |
| gorm | gorm:"primaryKey" |
ORM field constraints |
| validator | validate:"required" |
Field validation |
| yaml | yaml:"name" |
YAML serialization |
7. Nested Structures
(1) Nested structs (composition rather than inheritance)
Go does not have inheritance; it uses nested structs to achieve composition:
package main
import "fmt"
type Address struct {
City string
Country string
}
type User struct {
Name string
Age int
Address Address // nested Address
}
func main() {
u := User{
Name: "Alice",
Age: 28,
Address: Address{
City: "Shanghai",
Country: "China",
},
}
// Accessing nested fields
fmt.Println(u.Address.City) // Shanghai
fmt.Println(u.Address.Country) // China
}
(2) Anonymous Nesting (Field Promotion)
package main
import "fmt"
type Address struct {
City سلسلة
Country سلسلة
}
// Anonymous nesting: fields are "promoted"
type User struct {
Name سلسلة
Age int
Address // equivalent to Address Address, but without the field name
}
func main() {
u := User{
Name: "Alice",
Age: 28,
Address: Address{
City: "Shanghai",
Country: "China",
},
}
// Field promotion: direct access, without u.Address.City
fmt.Println(u.City) // Shanghai
fmt.Println(u.Country) // China
// You can also use the full path
fmt.Println(u.Address.City)
}
▶ Example: Nested + JSON Tag in Practice
package main
import (
"encoding/json"
"fmt"
)
type Address struct {
City string `json:"city"`
Country string `json:"country"`
}
type User struct {
Name string `json:"name"`
Age int `json:"age"`
Email string `json:"email,omitempty"`
Address Address `json:"address"`
}
func main() {
u := User{
Name: "Alice",
Age: 28,
Email: "alice@example.com",
Address: Address{
City: "Shanghai",
Country: "China",
},
}
data, _ := json.MarshalIndent(u, "", " ")
fmt.Println(string(data))
}
Output:
{
"name": "Alice",
"age": 28,
"email": "alice@example.com",
"address": {
"city": "Shanghai",
"country": "China"
}
}
8. Choosing Between map and slice
(1) Model Selection Reference Table
| Dimension | slice []T |
map map[K]V |
|---|---|---|
| Lookup Method | Linear Scan O(n) | Hash O(1) |
| Is it ordered? | ✅ Ordered | ❌ Unordered |
| Memory usage | Compact (24 + len*size) | Less compact (hash table + bucket) |
| Delete an element | Requires manual movement | delete(m, k) O(1) |
| Typical Scenarios | Lists/Queues/Stacks/Sorting | Dictionaries/Caches/Indexes |
(2) Model Selection Decision Tree
graph TB
A[Need to store multiple elements] --> B{Need to look up by key?}
B -->|Yes| C[Use map<br/>O(1) lookup]
B -->|No| D{Need to maintain order?}
D -->|Yes| E[Use slice]
D -->|No| F{Element count < 100?}
F -->|Yes| G[Either slice or map works]
F -->|No| C
(3) Real-World Example: Student Grade Management
package main
import "fmt"
// Store student grades in a map (look up by student ID)
type GradeBook struct {
scores map[string]int // Student ID → Score
}
func (gb *GradeBook) Set(id string, score int) {
gb.scores[id] = score
}
func (gb *GradeBook) Get(id string) (int, bool) {
score, ok := gb.scores[id]
return score, ok
}
// Use a slice to store a list of scores (for sorting or calculating the average)
func average(scores []int) float64 {
if len(scores) == 0 {
return 0
}
sum := 0
for _, s := range scores {
sum += s
}
return float64(sum) / float64(len(scores))
}
func main() {
gb := &GradeBook{scores: make(map[string]int)}
gb.Set("S001", 95)
gb.Set("S002", 82)
gb.Set("S003", 67)
if s, ok := gb.Get("S001"); ok {
fmt.Printf("S001: %d\n", s)
}
// Collect all scores and calculate the average
allScores := []int{}
for _, s := range gb.scores {
allScores = append(allScores, s)
}
fmt.Printf("Average Score: %.2f\n", average(allScores))
}
Output:
S001: 95
Average Score: 81.33
9. Complete Example: E-commerce SKU Inventory System
Combine all the features of maps and structs to build a complete e-commerce inventory query system:
// inventory_system.go
package main
import (
"encoding/json"
"fmt"
"sort"
)
// Product
type Product struct {
SKU string `json:"sku"`
Name string `json:"name"`
Price float64 `json:"price"`
Stock int `json:"stock"`
Category string `json:"category"`
Tags []string `json:"tags,omitempty"` // Tags: new/hot/discount
}
// Inventory System
type Inventory struct {
products map[string]Product // SKU → Product
}
func NewInventory() *Inventory {
return &Inventory{products: make(map[string]Product)}
}
// Add product
func (inv *Inventory) Add(p Product) {
inv.products[p.SKU] = p
}
// Query (comma-ok)
func (inv *Inventory) Find(sku string) (Product, bool) {
p, ok := inv.products[sku]
return p, ok
}
// Update stock
func (inv *Inventory) UpdateStock(sku string, delta int) error {
p, ok := inv.products[sku]
if !ok {
return fmt.Errorf("SKU %s not found", sku)
}
newStock := p.Stock + delta
if newStock < 0 {
return fmt.Errorf("insufficient stock for %s: have %d, need %d",
sku, p.Stock, -delta)
}
p.Stock = newStock
inv.products[sku] = p
return nil
}
// Sort by price (unordered map → convert to slice)
func (inv *Inventory) ListByPrice() []Product {
list := make([]Product, 0, len(inv.products))
for _, p := range inv.products {
list = append(list, p)
}
sort.Slice(list, func(i, j int) bool {
return list[i].Price < list[j].Price
})
return list
}
// Filter by category
func (inv *Inventory) FindByCategory(category string) []Product {
var result []Product
for _, p := range inv.products {
if p.Category == category {
result = append(result, p)
}
}
return result
}
func main() {
inv := NewInventory()
// Initialize 100 SKUs
categories := []string{"electronics", "clothing", "food"}
for i := 0; i < 100; i++ {
inv.Add(Product{
SKU: fmt.Sprintf("SKU-%05d", i),
Name: fmt.Sprintf("Product-%d", i),
Price: float64(i%50 + 10),
Stock: 100 - i%30,
Category: categories[i%3],
})
}
// 1. Look up a single SKU
if p, ok := inv.Find("SKU-0050"); ok {
fmt.Printf("Found: %s, price=%.2f, stock=%d\n", p.Name, p.Price, p.Stock)
}
// 2. Update inventory
if err := inv.UpdateStock("SKU-0050", -10); err == nil {
fmt.Println("Stock updated successfully")
}
// 3. Filter by category
electronics := inv.FindByCategory("electronics")
fmt.Printf("\nElectronics products: %d\n", len(electronics))
// 4. Sort by price (top 3)
sorted := inv.ListByPrice()
fmt.Println("\nTop 3 cheapest:")
for i, p := range sorted[:3] {
fmt.Printf(" %d. %s: %.2f\n", i+1, p.Name, p.Price)
}
// 5. JSON Serialization (API Response)
if p, ok := inv.Find("SKU-0050"); ok {
data, _ := json.MarshalIndent(p, "", " ")
fmt.Printf("\nJSON output:\n%s\n", data)
}
}
Expected Output:
Found: Product-50, price=10.00, stock=70
Stock updated successfully
Electronics products: 34
Top 3 cheapest:
1. Product-0: 10.00
2. Product-30: 10.00
3. Product-60: 10.00
JSON output:
{
"sku": "SKU-0050",
"name": "Product-50",
"price": 10,
"stock": 60,
"category": "food"
}
ListByPrice method, sort.Slice uses a closure as the comparison function. The closure captures the list variable and calls it during each comparison.
10. Additional Example Sets
▶ Example: Performance Comparison Between Initializing with a map Literal and Using make
package main
import (
"fmt"
"time"
)
func main() {
// Literal initialization
start1 := time.Now()
m1 := map[string]int{
"a": 1, "b": 2, "c": 3, "d": 4, "e": 5,
"f": 6, "g": 7, "h": 8, "i": 9, "j": 10,
}
fmt.Printf("Literals: %v, %v\n", len(m1), time.Since(start1))
// make pre-allocation
start2 := time.Now()
m2 := make(map[string]int, 10)
m2["a"] = 1
m2["b"] = 2
m2["c"] = 3
m2["d"] = 4
m2["e"] = 5
m2["f"] = 6
m2["g"] = 7
m2["h"] = 8
m2["i"] = 9
m2["j"] = 10
fmt.Printf("make: %v, %v\n", len(m2), time.Since(start2))
}
Output:
Literals: 10, 1.2µs
make: 10, 850ns
▶ Example: Methods that accept struct values vs. methods that accept pointers
package main
import "fmt"
type Counter struct {
Value int
}
// Value receiver: operates on a copy; does not affect the original
func (c Counter) IncrementValue() Counter {
c.Value++
return c
}
// Pointer receiver: directly modifies the original
func (c *Counter) IncrementPointer() {
c.Value++
}
func main() {
c := Counter{Value: 10}
// Value receiver: must use the return value
c = c.IncrementValue()
fmt.Printf("After value receiver: %d\n", c.Value) // 11
// Pointer receiver: direct modification
c.IncrementPointer()
fmt.Printf("After pointer receiver: %d\n", c.Value) // 12
}
Output:
After value receiver: 11
After pointer receiver: 12
▶ Example: Demonstration of Concurrency Pitfalls in map
package main
import (
"fmt"
"time"
)
func main() {
m := make(map[string]int)
// Writing goroutine
go func() {
for i := 0; i < 1000; i++ {
m["key"] = i // write operation
}
}()
// Reading goroutine
go func() {
for i := 0; i < 1000; i++ {
_ = m["key"] // read operation
}
}()
time.Sleep(100 * time.Millisecond)
fmt.Println("Concurrent reads and writes may cause a panic (fatal error: concurrent map read and map write)")
}
Output (may cause a panic during runtime):
Concurrent reads and writes may cause a panic (fatal خطأ: concurrent map read and map write)
sync.Map or sync.Mutex; this is covered in detail in Lesson 16.
❓ FAQ
reflect.DeepEqual() for a deep comparison.json:"name") or using the wrong type of quotes (double quotes must be used).sync.Map (Lesson 16) or add a mutex (Lesson 16).📖 Summary
- A map provides O(1) key-value lookups and is the core of Go's collection types
- The "comma-ok" syntax distinguishes between "zero values" and "non-existence," making it a safe approach for map queries
- Structs aggregate data using fields and support nested composition (as an alternative to inheritance)
- The struct tag is key to JSON/ORM serialization;
json:"name"is the most common format - map vs slice selection: look up by key → map; maintain order / sort → slice; both are often combined
- Both map and struct are wrapped in value semantics, but map is a reference type (shares the underlying data), while struct is a value type (copied when passed as an argument)
- Large structs or structs that need to be modified must use pointers (
func (s *Struct) Method())
📝 Exercises
-
Basic Problem (Difficulty ⭐): Use
map[string]intto count the number of times each word appears in a piece of text. Given the input"the quick brown fox jumps over the lazy dog the", the output should bemap[the:3 quick:1 brown:1 ...]. -
Advanced Problem (Difficulty ⭐⭐): Define a
Studentstruct (Name string, Scores []int) and implement the following methods: (1)Average() float64to calculate the average score; (2)Grade() stringto return a grade (A/B/C/D/F); (3) Test with at least 3 students. -
Challenge Problem (Difficulty ⭐⭐⭐): Implement a contact list app: Store contacts using
map[string]Contact(whereContactincludes Name, Phone, Email, and Group); implement (1) add, delete, and search; (2) filtering by group (family/friends/work); (3) exporting to a JSON file (usingos.WriteFile). Requirements: complete error handling + JSON tags + at least 10 test contacts.