Skip to content

Go Maps and Structs — map struct Tags and Zero Values Explained

DodaTech Updated 2026-06-28 7 min read

In this tutorial, you will learn about Go Maps and Structs. We cover key concepts, practical examples, and best practices to help you master this topic.

Go maps provide key-value storage with O(1) average lookup, structs group related fields with optional tags for JSON serialization, and both types have zero values for safe initialization.

What You'll Learn

  • Creating and using maps
  • Struct definition, fields, and tags
  • Nested structs and composition
  • JSON serialization with struct tags

Why It Matters

Maps and structs are fundamental data structures. Docker uses structs for container configuration and maps for labels. Kubernetes API objects are deeply nested structs. DodaZIP uses structs for archive metadata. Understanding these types is essential for real-world Go.

Real-World Use

A web API handler decodes JSON into structs, stores data in maps for Caching, and returns JSON-encoded struct responses. A CLI tool uses structs for configuration with JSON tags for file serialization.

flowchart LR
    A["Maps & Structs"] --> B["Maps"]
    B --> C["Structs"]
    C --> D["Tags"]
    D --> E["JSON"]
    A:::current --> B
    style A fill:#2563eb,stroke:#2563eb,color:#fff
    style B fill:#dbeafe,stroke:#2563eb,color:#1e40af
    style C fill:#dbeafe,stroke:#2563eb,color:#1e40af
    style D fill:#dbeafe,stroke:#2563eb,color:#1e40af
    style E fill:#f1f5f9,stroke:#94a3b8,color:#64748b

Maps

Maps are Go's built-in key-value data structure:

// Declare and initialize
var scores map[string]int          // nil map
scores = make(map[string]int)       // Empty map

// Map literal
scores := map[string]int{
    "Alice": 95,
    "Bob":   87,
    "Charlie": 92,
}

// Access
fmt.Println(scores["Alice"])  // 95

// Add/update
scores["David"] = 88

// Delete
delete(scores, "Bob")

// Check existence
score, exists := scores["Alice"]
if exists {
    fmt.Println("Score:", score)
}

Map Operations

ages := map[string]int{
    "Alice": 25,
    "Bob":   30,
}

// Length
fmt.Println(len(ages))

// Iteration
for name, age := range ages {
    fmt.Printf("%s is %d\n", name, age)
}

// Keys only
for name := range ages {
    fmt.Println(name)
}

// Values only
for _, age := range ages {
    fmt.Println(age)
}

Map with Any Key Type

// Keys can be any comparable type (not slices, maps, or functions)
type Key struct {
    ID   int
    Name string
}

cache := make(map[Key]string)
cache[Key{1, "Alice"}] = "data1"
fmt.Println(cache[Key{1, "Alice"}])  // data1

Structs

Structs group related fields together:

type Person struct {
    Name string
    Age  int
    City string
}

func main() {
    // Zero value
    var p Person
    fmt.Println(p)  // { 0 }

    // Field assignment
    p.Name = "Alice"
    p.Age = 25
    p.City = "New York"
    fmt.Println(p)  // {Alice 25 New York}

    // Struct literal
    p2 := Person{
        Name: "Bob",
        Age:  30,
        City: "London",
    }

    // Positional (not recommended)
    p3 := Person{"Charlie", 35, "Tokyo"}
}

Struct Methods

type Rectangle struct {
    Width  float64
    Height float64
}

func (r Rectangle) Area() float64 {
    return r.Width * r.Height
}

func (r Rectangle) Perimeter() float64 {
    return 2 * (r.Width + r.Height)
}

func main() {
    r := Rectangle{Width: 5, Height: 3}
    fmt.Println("Area:", r.Area())          // 15
    fmt.Println("Perimeter:", r.Perimeter()) // 16
}

Struct Tags

Tags provide metadata for struct fields, commonly used for serialization:

type User struct {
    ID        int       `json:"id"`
    Name      string    `json:"name"`
    Email     string    `json:"email,omitempty"`
    CreatedAt time.Time `json:"created_at"`
    Password  string    `json:"-"`
}

JSON Serialization

package main

import (
    "encoding/json"
    "fmt"
)

type Product struct {
    ID    int     `json:"id"`
    Name  string  `json:"name"`
    Price float64 `json:"price"`
}

func main() {
    p := Product{ID: 1, Name: "Widget", Price: 9.99}

    // Marshal to JSON
    data, _ := json.Marshal(p)
    fmt.Println(string(data))  // {"id":1,"name":"Widget","price":9.99}

    // Pretty print
    data, _ = json.MarshalIndent(p, "", "  ")
    fmt.Println(string(data))

    // Unmarshal from JSON
    jsonStr := `{"id":2,"name":"Gadget","price":19.99}`
    var p2 Product
    json.Unmarshal([]byte(jsonStr), &p2)
    fmt.Println(p2)  // {2 Gadget 19.99}
}

Nested Structs

type Address struct {
    Street  string `json:"street"`
    City    string `json:"city"`
    Country string `json:"country"`
}

type Employee struct {
    Name    string  `json:"name"`
    Age     int     `json:"age"`
    Address Address `json:"address"`
}

func main() {
    emp := Employee{
        Name: "Alice",
        Age:  25,
        Address: Address{
            Street:  "123 Main St",
            City:    "New York",
            Country: "USA",
        },
    }

    data, _ := json.MarshalIndent(emp, "", "  ")
    fmt.Println(string(data))
}

Anonymous (Embedded) Fields

type Contact struct {
    Email string
    Phone string
}

type Person struct {
    Name string
    Age  int
    Contact  // Embedded struct
}

func main() {
    p := Person{
        Name: "Alice",
        Age:  25,
        Contact: Contact{
            Email: "alice@example.com",
            Phone: "+1-555-0100",
        },
    }

    p.Email = "new@example.com"  // Promoted field
    fmt.Println(p.Email)         // new@example.com
}

Common Mistakes

1. Writing to a Nil Map

var m map[string]int
m["key"] = 42  // Panic! Assignment to nil map
m = make(map[string]int)
m["key"] = 42  // OK

2. Assuming Map Iteration Order

Map iteration order is random in Go. Don't rely on insertion order:

m := map[string]int{"a": 1, "b": 2, "c": 3}
for k, v := range m {
    fmt.Println(k, v)  // Order varies each run
}

3. Using Struct Without Initialization

type Point struct {
    X, Y int
}

var p Point
p.X = 5
p.Y = 10  // OK — zero values are valid

4. Copying a Struct with Locks or Pointers

type SafeCounter struct {
    mu sync.Mutex
    val int
}
// Copying this struct copies the mutex (don't do it)
// Use pointer receiver instead

5. Forgetting JSON Tags

Without tags, JSON fields match struct field names (capitalized). Always add JSON tags for proper naming.

Practice Questions

1. How do you check if a key exists in a map?

Use the two-value assignment: value, exists := myMap["key"]. If the key doesn't exist, value is the zero value and exists is false.

2. What are struct tags used for?

Struct tags provide metadata for fields, typically for serialization (JSON, XML, YAML), validation, or database column mapping. They're accessed via Reflection.

3. How do you embed a struct in another struct?

Declare it without a field name: type Person struct { Name string; Contact Contact }. The embedded struct's fields are promoted to the outer struct.

4. Can a map have a struct as a key?

Yes, if the struct fields are all comparable (no slices, maps, or functions in the struct).

Challenge: Create a struct for a blog post with JSON tags, nested author struct, and a map of tags. Marshal to JSON and back.

Solution
package main

import (
    "encoding/json"
    "fmt"
)

type Author struct {
    Name  string `json:"name"`
    Email string `json:"email"`
}

type Post struct {
    Title    string            `json:"title"`
    Content  string            `json:"content"`
    Author   Author            `json:"author"`
    Tags     map[string]bool   `json:"tags"`
    Draft    bool              `json:"draft"`
}

func main() {
    post := Post{
        Title:   "Go Maps & Structs",
        Content: "Maps and structs are essential...",
        Author:  Author{Name: "Alice", Email: "alice@example.com"},
        Tags:    map[string]bool{"go": true, "tutorial": true},
        Draft:   false,
    }

    data, _ := json.MarshalIndent(post, "", "  ")
    fmt.Println(string(data))
}

Expected JSON output:

{
  "title": "Go Maps & Structs",
  "content": "Maps and structs are essential...",
  "author": {
    "name": "Alice",
    "email": "alice@example.com"
  },
  "tags": {
    "go": true,
    "tutorial": true
  },
  "draft": false
}

FAQ

{{< faq question="When should I use a map vs a struct?" >}} Use maps for dynamic key-value data where keys aren't known at compile time. Use structs for fixed sets of fields known at compile time (API request/response, configuration, database rows). {{< /faq >}}

{{< faq question="Are maps thread-safe in Go?" >}} No. Concurrent reads and writes to a map cause a panic. Use sync.RWMutex or sync.Map for concurrent access. {{< /faq >}}

{{< faq question="Can I compare structs with ==?" >}} Yes, if all fields are comparable (no slices, maps, or functions). Comparable structs can be used as map keys. {{< /faq >}}

{{< faq question="What is the difference between struct and map initialization?" >} Structs use field names or positions. Maps use key-value pairs. Structs are type-checked at compile time; maps are dynamic. {{< /faq >}}

{{< faq question="How do I access struct tags at runtime?" >} Use the reflect package: t := reflect.TypeOf(myStruct); field, _ := t.FieldByName("MyField"); tag := field.Tag.Get("json"). {{< /faq >}}

Try It Yourself

package main

import (
    "encoding/json"
    "fmt"
)

type Config struct {
    Host    string `json:"host"`
    Port    int    `json:"port"`
    Debug   bool   `json:"debug"`
    Timeout int    `json:"timeout_secs"`
}

func main() {
    config := Config{
        Host:    "localhost",
        Port:    8080,
        Debug:   true,
        Timeout: 30,
    }

    data, _ := json.MarshalIndent(config, "", "  ")
    fmt.Println("JSON:")
    fmt.Println(string(data))

    var decoded Config
    json.Unmarshal(data, &decoded)
    fmt.Printf("Decoded: %+v\n", decoded)
    fmt.Printf("Host: %s, Port: %d\n", decoded.Host, decoded.Port)
}

What's Next

Now that you understand maps and structs, learn about pointers for passing by reference.

Topic Description Link
Go Pointers &, *, nil, new, pointer receivers {{< ref "09-pointers" >}}
Go Methods Receiver types, value vs pointer {{< ref "10-methods" >}}
Rust Structs Compare Rust struct patterns Rust

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro

Home Browse Go