Structs in Go
Defining custom data types by grouping fields together, since Go has no classes.
2 min read
Go has no classes. Instead, custom data types are built with struct — a way to group related fields under one named type — and behavior is attached separately via methods, covered in the next lesson.
Defining and creating a struct
type User struct {
Name string
Email string
Age int
}
user := User{
Name: "Ada Lovelace",
Email: "ada@example.com",
Age: 28,
}
fmt.Println(user.Name) // Ada LovelaceField names starting with a capital letter are exported (visible to other packages), following the same capitalization rule covered in the variables lesson. user.Age = 29 updates a field directly, since structs in Go are mutable by default.
Struct literals, shorthand
user := User{"Ada Lovelace", "ada@example.com", 28} // positional -- fragile, order-dependent
user2 := User{Name: "Grace Hopper"} // Email and Age get zero valuesThe positional form works but is brittle — reordering the struct's fields silently breaks every positional literal. Named fields are the idiomatic default; positional literals are mostly seen in very small, stable structs.
Structs are value types
This is the detail that trips people up most coming from Java or JavaScript: assigning or passing a struct copies it.
user1 := User{Name: "Ada"}
user2 := user1
user2.Name = "Grace"
fmt.Println(user1.Name) // still "Ada" -- user2 was a separate copyIf you want changes to propagate — say, inside a function meant to modify the original — you need a pointer:
func birthday(u *User) {
u.Age++
}
birthday(&user) // pass the address; birthday mutates the original&user takes the address of user, and inside the function *User is "a pointer to a User." Go conveniently lets you write u.Age instead of the more explicit (*u).Age — the dot operator automatically dereferences pointers to structs.
Nested structs
type Address struct {
City string
Country string
}
type User struct {
Name string
Address Address
}
user := User{
Name: "Ada",
Address: Address{City: "London", Country: "UK"},
}
fmt.Println(user.Address.City) // LondonEmbedding: Go's alternative to inheritance
Go has no inheritance, but structs can embed other structs, which promotes the embedded struct's fields and methods to the outer struct directly:
type Base struct {
ID int
CreatedAt string
}
type Article struct {
Base // embedded, no field name
Title string
}
a := Article{Base: Base{ID: 1, CreatedAt: "2024-01-01"}, Title: "Hello"}
fmt.Println(a.ID) // promoted directly from Base
fmt.Println(a.Title)This gives you composition-based code reuse — "an Article has the fields of a Base" — without the tight coupling and fragile hierarchies that class inheritance tends to accumulate over time. It's a deliberate design choice: Go favors "compose small pieces together" over "extend a shared base class," and structs plus embedding are how that plays out for data.
Structs on their own just hold data. The next lesson covers methods and interfaces — how Go attaches behavior to these types and expresses shared behavior across otherwise-unrelated types.
Test what you just learned
4 quick questions. Get all of them right to unlock the next lesson.
You can take the quiz without an account — logging in just lets your result count toward your progress.