LearnAI ToolsCareerPractice BuildsPlayContact
Lesson 2517 min read

Structs

Learn how to define Go structs, create struct literals, nest structs inside one another, and use anonymous structs.

Introduction

A struct is a composite type that groups together related fields under one name. If you've used a class in another language purely to hold data, or an object literal to group properties, a Go struct will feel familiar — it's the primary building block for modeling real-world data in Go.

What You Will Learn
  • How to define a struct type.
  • How to create struct values using struct literals.
  • How to access and modify struct fields.
  • How to nest one struct inside another.
  • How to use anonymous structs for quick, one-off data grouping.
  • Why structs are value types, like arrays.

Defining a Struct

A struct type is defined with the type keyword, a name, and the struct keyword followed by a list of fields, each with a name and a type.

package main
import "fmt"
type Book struct {
Title string
Author string
Pages int
InPrint bool
}
func main() {
var b Book // zero-valued struct
fmt.Printf("%+v\n", b)
}
Output

Click Run to see what this code prints.

The %+v verb prints field names alongside their values, which is useful for debugging. Notice every field starts at its type's zero value: empty strings, 0 for int, and false for bool.

Struct Literals

A struct literal lets you create and initialize a struct value in one expression. The keyed form (field: value) is strongly preferred in idiomatic Go, since it's self-documenting and resilient to field reordering.

package main
import "fmt"
type Book struct {
Title string
Author string
Pages int
InPrint bool
}
func main() {
// Keyed literal — the recommended style.
b1 := Book{
Title: "The Go Programming Language",
Author: "Donovan & Kernighan",
Pages: 380,
InPrint: true,
}
// Positional literal — must match field order exactly, fragile.
b2 := Book{"Go in Action", "Kennedy et al.", 300, true}
fmt.Printf("%+v\n", b1)
fmt.Printf("%+v\n", b2)
}
Output

Click Run to see what this code prints.

Accessing and Modifying Fields

Struct fields are accessed and assigned using dot notation.

package main
import "fmt"
type Book struct {
Title string
Pages int
}
func main() {
b := Book{Title: "Go Basics", Pages: 150}
fmt.Println(b.Title)
b.Pages = 175 // update a field directly
fmt.Println(b.Pages)
}
Output

Click Run to see what this code prints.

Nested Structs

A struct field can itself be another struct type, letting you model hierarchical data naturally. Fields on the nested struct are reached by chaining dot notation.

package main
import "fmt"
type Address struct {
City string
Country string
}
type Customer struct {
Name string
Address Address // nested struct field
}
func main() {
c := Customer{
Name: "Priya Nair",
Address: Address{
City: "Bengaluru",
Country: "India",
},
}
fmt.Println(c.Name)
fmt.Println(c.Address.City, c.Address.Country)
c.Address.City = "Mumbai" // update a nested field
fmt.Println(c.Address.City)
}
Output

Click Run to see what this code prints.

Anonymous Structs

When you need a one-off struct without declaring a named type — for example, for a quick local grouping of values or a small test fixture — Go lets you define an anonymous struct inline.

package main
import "fmt"
func main() {
point := struct {
X int
Y int
}{X: 3, Y: 7}
fmt.Println(point)
fmt.Println(point.X, point.Y)
// Common in table-driven tests: a slice of anonymous structs.
cases := []struct {
Input int
Expected int
}{
{Input: 2, Expected: 4},
{Input: 3, Expected: 9},
}
for _, c := range cases {
fmt.Printf("square(%d) should be %d\n", c.Input, c.Expected)
}
}
Output

Click Run to see what this code prints.

Structs Are Value Types

Like arrays, structs are value types — assigning a struct to a new variable, or passing it to a function, copies all of its fields. This is a natural extension of what you already learned with arrays.

package main
import "fmt"
type Point struct {
X, Y int
}
func main() {
original := Point{X: 1, Y: 2}
copy := original // full copy
copy.X = 100
fmt.Println("original:", original)
fmt.Println("copy: ", copy)
}
Output

Click Run to see what this code prints.

Looking Ahead

When a struct is large or needs to be mutated through a function, you'll typically pass a pointer to it — exactly as you saw in the previous lesson on pointers. You'll use this pattern constantly once you start writing methods in the next lesson.

Common Mistakes

Avoid These Mistakes
  • Using positional struct literals for structs with many fields, which becomes fragile and unreadable as fields are added or reordered.
  • Expecting a struct passed by value into a function to be mutated by that function — pass a pointer instead if mutation is needed.
  • Forgetting that comparing structs with == only works if every field is itself comparable (no slices or maps as fields).
  • Deeply nesting nested-struct field access without helper methods, hurting readability.
  • Overusing anonymous structs for data that's reused in multiple places — define a named type instead once it's used more than once.

Best Practices

  • Always use keyed struct literals (Field: value) in production code for clarity and safety against field reordering.
  • Group related fields into nested structs (like Address inside Customer) to keep types organized and reusable.
  • Reach for anonymous structs for throwaway groupings, especially in table-driven tests.
  • Use %+v in fmt.Printf when debugging struct values, so field names are visible alongside their values.
  • Pass large structs by pointer to functions that need to mutate them or to avoid copy overhead.

Frequently Asked Questions

Yes, as long as every field is itself comparable. Structs containing slices, maps, or functions as fields cannot be compared with ==.

Only if you want them exported (accessible from other packages). A capitalized field name like Title is exported; a lowercase one like title is package-private.

A struct has a fixed set of named fields, each potentially with a different type, known at compile time. A map holds an arbitrary number of key-value pairs, all sharing the same value type, determined at runtime.

Key Takeaways

  • A struct groups related fields together under a single named type.
  • Keyed struct literals (Field: value) are the preferred, safer way to create struct values.
  • Struct fields are accessed and modified with dot notation, including chained access for nested structs.
  • Anonymous structs are useful for one-off groupings that don't need a reusable named type.
  • Structs are value types — assignment and function calls copy all their fields, just like arrays.

Summary

Structs are how you'll model almost every piece of real-world data in Go — users, products, configuration, API responses, and more. Now that you can define and use structs, the next step is giving them behavior: you'll learn how to attach methods to struct types.

Next Lesson →

Methods