AlgoMaster Logo

Struct Literals & Initialization

Last Updated: May 17, 2026

14 min read

Declaring a struct type tells the compiler what fields exist, but every program eventually needs actual values: a real Product with a name and a price, a real Order with a customer attached. This lesson covers all the ways to build a struct value in Go: named field literals, positional literals, partial initialization, empty literals, nested structs, the address-of shortcut for getting a pointer, and how to populate slices and maps of structs. Picking the right form keeps your code readable and protects you from a class of silent bugs when the struct grows new fields later.

Named Field Literals

The most common way to build a struct value is a named field literal, where each field is listed by name with its value:

The syntax is the type name followed by braces, with FieldName: value pairs separated by commas. The order you list the fields in the literal doesn't have to match the order they appear in the struct definition. Each pair is matched by name, so writing Stock: 12 before Name: "Go in Action" produces the same value.

Trailing commas matter in Go. When you spread a struct literal across multiple lines, the last field needs a comma after it, otherwise the parser's automatic semicolon insertion ends the statement at the wrong place. Single-line literals don't need (and can't have) a trailing comma:

Both forms are valid. Most Go code uses the multi-line form for anything with more than two or three fields, because it diffs cleanly when fields change.

Named literals are self-documenting. A reader scanning the code sees exactly which value goes into which field without having to remember the field order. That readability is the whole reason this form is the default in Go codebases.

Positional Literals

A positional literal leaves out the field names and provides values in the exact order the fields appear in the struct definition. Every field must be supplied:

The compiler matches "Go in Action" to Name, 24.99 to Price, and 12 to Stock by position. Three values, three fields, in order.

Positional literals look concise, but they're brittle. The struct definition decides the order, so changing the order of fields in type Product struct silently changes the meaning of every positional literal in the codebase. Worse, if you add a new field in the middle of the struct, every positional literal stops compiling and you have to update them all. The error you'd get from the compiler is helpful, but it's still mechanical work that named literals would have avoided.

The compiler stops you from accidentally leaving a field out, which is the one safety net positional literals have. The other failure mode (passing values of the wrong type in the wrong slot) is also caught, as long as the types differ:

The types don't line up, so the compiler complains. The truly dangerous case is when two adjacent fields share a type. Two float64 fields in a row, for example, will accept either ordering silently:

The compiler accepts whichever order you write, because the types match either way. A reader has to look up the struct definition to know which number means what. Named literals never have this problem.

When to Use Named vs Positional

The Go style is straightforward: prefer named field literals everywhere. Reach for positional literals only when the struct is small, the fields are unlikely to change, and the field order is obvious from context. A 2-field Point struct with X and Y is the textbook case for positional literals because everyone reads Point{3, 4} correctly. Most real-world structs aren't Point.

FormProsConsWhen to Use
NamedSelf-documenting, robust to field additions/reordering, allows partial initSlightly more typingDefault choice for almost everything
PositionalCompactBrittle to struct changes, requires all fields, easy to swap same-typed fieldsTiny stable structs (2-3 fields) like Point, or test data with very obvious layout

Linters and tools agree. The vet tool ships with a check called composites that warns when you use a positional literal for a struct defined in another package. That covers the most dangerous case: positional literals across package boundaries, where you can't see the field order at a glance and a maintainer of the other package could reorder fields without thinking about your code.

Partial Initialization with Named Literals

One advantage of named literals is that you don't have to supply every field. Any field you skip gets its zero value: 0 for numbers, "" for strings, false for booleans, nil for slices/maps/pointers, and the recursive zero value for nested structs.

Stock and Featured weren't mentioned, so they took their zero values: 0 and false. The %+v verb prints the field names alongside the values, which is useful for confirming exactly what was set and what wasn't.

This is one of the design ideas Go takes seriously: every type has a sensible zero value, and the empty form of a struct is usable right away. You don't have to write a constructor just to create an "empty" Product that's safe to inspect. A freshly declared Product is a valid Product with zero stock and a price of zero.

Partial initialization shows up everywhere in real code. A function that takes a configuration struct typically populates a few fields and leaves the rest at their defaults:

MinPrice defaults to 0 (no floor), and OnSale defaults to false (don't restrict to sale items). That matches what a user with no opinion on those fields would want. Building filters this way keeps the call sites short and gives you defaults for free.

The flip side is that the zero value has to make sense for your struct. If 0 doesn't mean "no price floor" but instead means "items priced at exactly $0", you have a problem. The cleanest fix is to pick zero values that work as defaults, often by reframing the field (using *float64 to make "not set" explicit, or making "0" actually mean "no limit"). Constructor functions are the bigger lever when zero values won't work.

The Empty Literal

The shortest named literal is Product{} with no fields at all. Every field gets its zero value:

Product{} is identical to writing var empty Product. Both produce a fully zeroed struct. The difference is stylistic: var empty Product reads as a declaration, while empty := Product{} reads as an explicit construction. Most Go developers use var when they're going to populate the struct later and the literal form when they want to make "freshly-zeroed" obvious at the use site.

The empty literal is also the canonical way to express "I want a zero-valued struct as a parameter" or "as a return value", inline, without naming a variable:

The function returns Cart{} and the caller gets a fully-zeroed cart back. Notice that Items is a nil slice (zero value for a slice), but len reports 0 and ranging over it does nothing, so most code treats it the same as an empty slice. That's one of the reasons Go's zero values are usable: a nil slice behaves like an empty slice in almost every operation, so a zero-valued Cart is already a working "empty cart".

Nested Struct Literals

Real-world data is rarely flat. An Order has a customer, a customer has an address, an address has a country. Go handles nesting by letting one struct have another struct as a field. The literal syntax nests right alongside the type:

The Customer field is a full Customer value, built with its own literal. The two literals nest, with the inner one sitting in the slot where its type appears. Reading the code top-down, the structure of the literal mirrors the structure of the data.

You can nest as deep as the type allows. Add a shipping address to the order and the third level looks the same:

Each nested literal carries its own type name and braces. The indentation tracks the nesting visually, which helps when an order grows half a dozen sub-fields. Accessing a nested field uses the dot operator chained: order.Customer.Address.City.

You can also use the empty literal for a nested struct when you don't have data for it yet:

Customer{} fills the slot with a zero-valued customer (empty name, empty email, zero-valued address). The Address inside it also gets zero values recursively, because zero values are defined for every type.

If you omit Customer entirely, you get the same thing through the partial-initialization rule:

order.Customer ends up as the zero Customer, which contains a zero Address. The two forms are equivalent. Use the explicit Customer: Customer{} when you want to signal "I deliberately set this to empty", and the omitted form when "zero value is fine, no need to mention it" is the better story.

This lesson focuses on plain field-based nesting where one struct has another struct as a named field. Go has a separate feature called embedding (anonymous fields) that lets you promote the inner struct's fields onto the outer one. For now, nesting always means "one struct holds another as a regular named field".

Address-Of Literals (&Product{...})

Sometimes you want a pointer to a struct value rather than the struct value itself. Functions that mutate the struct, methods with pointer receivers, and storage in data structures that hold pointers all need a *Product, not a Product. The shortest way to produce one is the *address-of literal*:

The & in front of the literal returns the address of the value that the literal would have produced. The type of book is *Product (a pointer to a Product), not Product. Go automatically dereferences the pointer when you use the dot operator on it, so book.Name works the same way it would on a non-pointer value.

&Product{...} is equivalent to:

The shorter form is what you'll see in practice. It's a single expression that builds a value and yields its address, often used to populate fields that are typed as pointers or to return pointers from constructor-like functions.

This lesson stays at the syntax level. The deeper questions (when to use a pointer instead of a value, what the address even means, how pointers interact with the garbage collector) belong elsewhere. For now, the only thing to know is the syntax: & before a struct literal yields a pointer to a fresh struct, and that pointer is usable immediately.

Slices of Structs

Most real programs work with collections of structs: a list of products in a catalog, a list of items in a cart, a list of orders for a customer. The literal form for a slice of structs nests cleanly:

The outer []Product{...} is the slice literal. Inside, each element is a struct literal, but you can leave off the type name on each element because the compiler already knows the slice's element type. Writing Product{Name: "..."} for each element works too, but the type-elided form is the standard Go style.

Positional literals also work inside slices, with the same trade-offs as before:

This is compact and the field meanings are obvious to anyone who's seen the Product definition recently. For a long-lived slice in production code, the named form is safer; for a test fixture or an example, positional is fine.

You can also build an empty slice and append structs into it one at a time:

append works on []Product the same way it works on any other slice. Each call adds one struct to the slice, growing the backing array when needed.

Maps of Structs

The same nesting works for maps. A map[string]Product lets you look up products by code in constant time:

The map literal has key-value pairs. Each value is a struct literal, again with the type name elided because the map's value type already says it's a Product. The lookup catalog["BOOK-02"] returns the struct value (a copy) along with a boolean for whether the key was present.

Maps of structs have one wrinkle worth flagging. You can't assign to a field of a map value directly:

Because the map returns a copy of the struct on each lookup, writing to a field of that copy wouldn't change the map's stored value, so Go disallows the syntax entirely. The fix is to read the struct out, modify it, and put it back:

If you find yourself doing this a lot, switch the map to map[string]*Product, where the values are pointers. Then catalog["BOOK-01"].Stock = 11 works because you're modifying the struct that the pointer references, not a copy. For now, know that maps of structs by value are fine for read-mostly lookups, and pointer-valued maps are the move when you need to mutate.

How the Forms Compose

It helps to see the building blocks lined up. The basic operations and what they produce:

LiteralWhat You GetUse For
Product{Name: "X", Price: 1}A Product value (partial init OK)Most struct construction
Product{"X", 1, 5}A Product value (all fields required)Tiny stable structs only
Product{}A zero-valued ProductExplicit "empty"
&Product{Name: "X"}A *Product (pointer)When a pointer is needed
Order{Customer: Customer{Name: "Y"}}Nested struct valueComposed data
[]Product{ {"A", 1, 5}, {"B", 2, 3} }A slice of ProductCatalogs, lists
map[string]Product{"A": {Name: "X"}}A map of Product valuesLookup by key

These compose freely. A slice of orders, where each order contains a customer with an address, would use slice-literal-of-struct-literal-of-struct-literal, nested as deep as the data goes:

The structure on the page mirrors the structure of the data. Each level of nesting adds another pair of braces, and the named-field form keeps the meaning of each value obvious from the source code without a trip to the struct definition.

The slice holds two orders, each order owns a customer, each customer owns an address. The literal on the previous example builds exactly this tree in one expression.

Common Mistakes

A few traps come up often enough to call out explicitly.

Forgetting a Field with a Positional Literal

Positional literals demand every field. Drop one and the compiler stops you:

The compiler's message says exactly what's wrong. The fix is either to supply the missing value (Product{"Go in Action", 24.99, 0}) or to switch to named form, which lets you skip fields:

Field Order Mismatch in Positional Literals

When two adjacent fields share a type, positional literals will accept either ordering with no warning. Here's the kind of bug that survives compilation:

What's wrong with this code?

Was the developer trying to say "20% off, no real minimum spend" or "5% off, minimum $20 cart"? Both are reasonable readings of Coupon{20.0, 5.0}. The compiler can't tell, because both values are float64. Six months later, when somebody applies a 5% coupon and a customer gets 20% off, you have a bug that's a real pain to trace.

Fix:

Named fields make the meaning explicit at the call site. Anybody reading the code knows immediately which number is the discount and which is the spend floor.

Adding a Field Later Breaks Positional Literals

When the codebase grows and you add a new field to a struct, every positional literal stops compiling at once:

This is annoying but at least loud. The genuinely dangerous case is inserting the new field in the middle of the struct definition:

Here the types catch it. If the new field had been string, the compiler would have happily slid 24.99 into Price, made 12 into a string-coerced... actually 12 wouldn't fit in a string either, so this particular case still fails. But you can construct examples where types align by accident and the wrong values end up in the wrong slots. Named literals are immune to this entire class of problems.

Forgetting & When a Pointer Is Needed

The flip side of &Product{...} is forgetting it when the API wants a *Product. The compiler error is clear, but worth recognizing:

The fix is update(&Product{Name: "old"}). The error message names both the actual and expected types, so it's usually a 5-second fix once you see it. You'll see the & shortcut a lot once you start writing methods.

Putting It Together

A short program that pulls in the main forms: a catalog as a slice of structs, an order with a nested customer struct, partial initialization, an empty literal, and the address-of form.

Every literal form shows up: a slice of structs with type elision on each element, a nested struct (Customer inside Order, Address inside Customer), partial initialization (Address with only Country set), the address-of form on a struct literal, and an empty Order{} as a placeholder. The two empty-string outputs come from the fields that were never set, which take their zero values silently.

Summary

  • The named field literal (Product{Name: "X", Price: 1}) is the default in Go: it's self-documenting, allows partial initialization, and survives struct changes. Prefer it almost everywhere.
  • The positional literal (Product{"X", 1, 5}) requires every field in declaration order. It's brittle when fields are added, reordered, or share a type with their neighbors. Use it only for tiny, stable structs.
  • Fields you don't mention in a named literal take their zero value (0, "", false, nil, or a zero-valued struct). This is what makes partial initialization possible.
  • The empty literal Product{} is the explicit form for "zero-valued struct". It's identical to var p Product, just used as an expression.
  • Nested structs use nested literals: each level gets its own Type{...} block. Skipping a nested field still works through partial init and produces a zero-valued nested struct.
  • &Product{...} is the address-of literal: it builds a Product value and yields a pointer to it in one expression. Use it whenever an API expects a *Product.
  • Slices and maps of structs nest the same way: []Product{ {Name: "A"}, {Name: "B"} } and map[string]Product{"K": {Name: "V"}}. The element type can be elided on each entry.
  • The two most common bugs are silent value-swapping in positional literals and direct field assignment through a map lookup (which doesn't compile). Named literals fix the first; reading-modifying-writing the whole struct (or using map[string]*Product) fixes the second.

The next lesson, Exported vs Unexported Fields, covers how the case of the first letter in a field name decides whether code outside the package can see it, and how that visibility rule shapes API design and JSON encoding.