Last Updated: May 17, 2026
An anonymous struct is a struct type declared inline at the point of use, with no type Name struct {...} declaration anywhere. You write the field list and the value in the same expression, and the type exists only for that one spot in the code. This lesson covers the syntax, the handful of places where they pull their weight (table-driven tests, ad-hoc JSON shapes, quick local groupings), and the limits that tell you when to give the struct a real name instead.
A named struct looks like this:
An anonymous struct has the same field list but no type line. You declare the type and the value together:
The variable p holds a struct value with two fields. The type itself has no name. You can read p.Name and p.Price exactly the way you would for a named struct, because at the value level there's no difference. The only thing missing is a reusable type identifier.
You can also separate the type and the value across two lines:
var p struct{...} declares p with the zero value of that anonymous struct type. Each field gets its own zero value: "" for Name, 0 for Price. From there you can assign fields normally.
Semicolons (or newlines) separate the fields in the type. On one line, you separate with semicolons:
That one-liner compiles and is occasionally useful for very small structs. It gets unreadable fast, so the multi-line form is the usual choice.
A named struct claims a name in the package namespace. Every time you write type Foo struct {...}, anyone reading the file has to wonder where Foo is used, whether it's exported, whether other packages depend on it, and what its long-term role in the design is. For a type that lives for three lines inside one function, that's a lot of overhead.
Anonymous structs let you skip the ceremony. The type exists only for the value you're building, so there's nothing to name, nothing to document, and nothing for someone else to reuse by accident. Three good fits stand out:
The pattern in all three: the shape matters for a few lines of code and nowhere else. Naming it would suggest it's meant to be reused, and it isn't.
This is the single most common use of anonymous structs in real Go code. The standard pattern for testing a function is to declare a slice of cases, each one a struct of (input, expected) pairs, and loop over them. Naming the test-case struct (something like type discountCase struct {...}) clutters the package without adding value, since the struct is only referenced inside that one test function. An anonymous struct fits perfectly.
Imagine a pricing function that applies a tiered discount: 0% under $50, 10% from $50 to $99.99, 20% at $100 or more.
The slice literal builds an inline anonymous-struct type and four values of that type, all in one expression. Each entry uses positional initialization because the struct only has three fields and the order is obvious in context. For a struct with more fields or where field meaning is less clear, prefer the named form {name: "under tier", price: 29.99, expected: 29.99}.
In real _test.go files the loop body calls t.Run(c.name, func(t *testing.T) { ... }) and uses t.Errorf on failure, but the structural pattern is identical: a slice of anonymous structs holding inputs and expected outputs, walked in a for loop.
Cost: the entire slice (header plus the four struct values) is allocated once at the start of the function. The struct values are inlined in the slice's backing array, not heap-allocated individually. For a few dozen test cases, the allocation is invisible.
You can add fields to the case struct as the function under test grows. Want to test that the discount label is correct too? Just add another field:
The case struct grew from three fields to four, and nothing outside the function had to change. There's no type line to maintain, no question about whether the field rename breaks other code. The struct shape is local to this one function.
The encoding/json package marshals Go structs to JSON and unmarshals JSON back into Go structs. When you're sending or receiving a one-off payload (a request body for a single API call, a response shape you only parse in one function), an anonymous struct lets you write the exact JSON shape inline without polluting the package with a named type.
This example builds a request body for placing an order with a downstream service:
The struct type only exists for the duration of the json.Marshal call. Once Marshal returns the JSON bytes, the type has done its job. The json:"..." struct tags rename each field for the JSON output, so the field is order_id in the JSON even though the Go field is OrderID. For now, treat them as a way to customize JSON field names.
The same trick works for decoding a response. Suppose the order service replies with a JSON document, and you only care about three fields:
The response has five fields. The anonymous struct only declares the three we care about, and json.Unmarshal silently ignores the extra fields in the input. This is one of the cleanest uses of anonymous structs: declare the exact shape you need, decode into it, and move on. No type pollutes the package for a payload that's never used anywhere else.
Cost: there's no extra runtime cost compared to a named struct. The compiler treats anonymous and named structs identically at the machine-code level. The benefit is purely about source-code clarity and namespace hygiene.
If the same JSON shape gets used in three or four places, that's the signal to promote it to a named type.
When you have a few related values inside one function and you want to treat them as a unit, an anonymous struct beats juggling parallel slices or stuffing everything into a map[string]any. The classic example is gathering some configuration from a few variables before passing it to another function.
Here's a checkout function that builds an inline config block to summarize the HTTP request it's about to make:
The config variable groups three related values. Without it, the function would either have three loose local variables (endpoint, timeout, retry) or a named type that doesn't earn its name. The anonymous struct keeps the values together and lets you pass them around as one unit inside the function.
Another typical case is computing an intermediate result with multiple parts and returning just one of them:
stats is initialized with its zero value (Count: 0, Total: 0, Max: 0), then updated as the loop runs. The anonymous struct keeps the three pieces of state bound together. Three separate int and float64 variables would have worked too, but the struct documents that these values belong together and makes it easier to pass stats to a helper later if needed.
A named struct can have a field whose type is an anonymous struct. The field still has a name, but its type is declared inline. This is different from struct embedding, where the field has no name at all and its type is a named type.
Stock is a regular field with a name. Its type happens to be an anonymous struct with two fields, declared inline. You access the inner fields with two-level dotted access: p.Stock.Available. There's no way to write p.Available for this case, because the inner type isn't promoted.
This is sometimes useful for grouping related fields that wouldn't make sense as a top-level named type. "Stock counts" might not be reusable enough to warrant a type Stock struct {...} declaration. The anonymous-field approach keeps the structure right there in the Product definition.
The line between "anonymous struct type as a field" and "struct embedding" is easy to confuse, so be explicit. Compare:
versus:
The first uses an anonymous struct type for a regular named field. The second uses a named type with no field name (the type itself becomes both the field name and the field type). For this lesson, the rule is: if you write FieldName struct { ... }, you have a named field with an anonymous-struct type, and access is always through FieldName.
Initialization of an anonymous struct field is verbose because the type has to be repeated:
That ceremony is the price of using an anonymous struct as a field type. The compiler can't refer to the inner type by name, so you spell it out again at the literal. Once you find yourself writing the same anonymous-struct shape in two places, that's a signal to give it a real name. The repetition is the cost; the lesson is that anonymous structs work best for shapes that appear exactly once.
Anonymous structs are deliberately less capable than named structs. Knowing where they fall short tells you when to give up and write type Foo struct {...}.
A method declaration needs a receiver type, and the receiver type must be a named type defined in the same package. An anonymous struct has no name, so there's nothing to declare a method on.
The compiler rejects this outright. If you need a method, you need a named type. This is the single most common reason to promote an anonymous struct to a named one.
A function parameter has to declare the type of its argument, and an anonymous struct type has to be respelled every time you mention it. The first awkward case is a helper function:
The same struct shape (Count int; Total float64) appears twice: once in the parameter list, once at the call site. Add a third field and you have to update both places. Add a fourth caller and the same type body shows up four times. At that point, a named type costs you one declaration and saves you the repetition everywhere else.
Go uses structural typing for anonymous struct types: two anonymous structs are the same type if (and only if) they have the same fields in the same order, with the same names, types, and tags. Different field order, different names, or different tags makes them different types, and you can't assign one to the other.
a and b hold values with the same fields and the same data, but in opposite declaration order. They are different types. Assigning a = b doesn't compile, because the compiler treats struct{Name string; Price float64} and struct{Price float64; Name string} as two distinct types.
That sounds harmless until you try to put two "matching" anonymous struct values into the same slice or map and get a type mismatch you weren't expecting. The fix is to declare the type once and use the variable's type consistently:
type item = struct {...} is a type alias (note the = sign). It doesn't define a new type, it just gives a local name to the anonymous struct so you can refer to it without rewriting the field list. The slice literal uses that name once, and each entry omits the type. This is a comfortable middle ground between "full anonymous struct everywhere" and "promote it to a real named type at package level."
The rule of thumb is simple: if the type is reused, name it. Specifically, reach for a named struct when any of these apply:
Product, an Order, a Customer). Domain types deserve names because their meaning matters, not just their shape.In short: anonymous structs are a tool for shapes that live and die in one spot. The moment a shape outgrows that spot, give it a name.
| Use anonymous struct when... | Use a named struct when... |
|---|---|
| Building a slice of test cases for one function | The struct represents a domain concept (Product, Order, Cart) |
| Encoding or decoding a one-off JSON payload | The same field list shows up in 2+ places |
| Grouping a few local values inside a function | You need methods on the type |
| Defining a sub-structure of a named struct (sparingly) | The type appears in a function signature |
| The shape is used exactly once | The type crosses a package boundary |
Here's a single program that uses anonymous structs in three of the patterns covered above: a slice of test cases, a JSON payload, and a quick local grouping. It also shows how the same program would look with a named struct for the parts that earn one.
Order is a named struct because the program treats it as a domain entity that other code would reasonably handle. The three anonymous structs each show up exactly once: the test cases for the discount function, the totals breakdown local to main, and the shipping-service payload. None of them deserves a name.
The diagram walks through the decision. Start with the question "do I really need this type more than once?". If the answer is no, ask whether you need methods, and whether the type crosses a function or package boundary. Anonymous structs only survive a "no" to every one of those questions.
type declaration, written as struct{ Field1 T1; Field2 T2 }{...} in one expression.The next lesson covers struct embedding, where a field has no name (only a type) and the embedded type's fields and methods get promoted to the outer struct. It looks superficially like the anonymous-struct-as-field pattern shown here, but the access rules and the design intent are different, and the comparison is worth keeping straight.