In Go, what is the difference between a keyed struct literal and a positional one?
answer
- Two ways to fill a struct's braces
- One form depends on declaration order
- Omitted fields versus missing values
- Reordering compiles; adding does not
- go vet checks this across packages
basics
~20 sA keyed struct literal names each field and may omit fields, so order does not matter. A positional literal must supply every field in declaration order, so reordering fields silently changes what each value means.
solid answer
~40 sA struct composite literal comes in two forms. Keyed, `User{ID: 1, Name: "ada"}`, names each field: order is free and any field you leave out gets its zero value. Positional, `User{1, "ada"}`, binds values to fields by position, so it must list every field, in declaration order. You cannot mix the two forms in one literal. Keyed is the house style because it survives changes to the struct: adding a field breaks a positional literal loudly (the compiler complains about too few values), but swapping two fields of the same type compiles fine and silently means something different. `go vet`'s composites check flags unkeyed literals of struct types imported from another package, precisely because you do not control when those fields move.
code
go · 14 linestype User struct {
ID int
Name string
OK bool
}
// positional: every field, in declaration order
a := User{1, "ada", true}
// keyed: order free, OK defaults to false
b := User{Name: "ada", ID: 1}
// compile error: cannot mix keyed and positional elements
// c := User{ID: 1, "ada", true}go deeper
Be ready to write both forms from memory and say which fields default to zero. Know that a keyed literal may omit fields and a positional one may not, and that the two forms cannot be mixed.
Explain the failure modes precisely: adding a field is a compile error, while reordering two same-typed fields compiles and silently swaps data. Mention that go vet's composites check covers imported struct types.
Show the review instinct. Point out unkeyed literals of a dependency's types in a diff, explain why generated code must emit keyed literals, and use a %#v dump to check what a construction site really produced.
Frame it as a contract question: a positional literal makes a struct's field order part of its API. Decide whether your own exported structs promise field order at all, and lean on vet in CI rather than review discipline.
## The two forms A composite literal builds a value of a struct, array, slice or map type by listing its elements inside braces. For a struct there are exactly two ways to write the element list, and Go does not let you blend them. **Positional.** `User{1, "ada", true}` binds the first value to the first declared field, the second to the second, and so on. You must supply a value for *every* field, in the order the fields are declared in the type. **Keyed.** `User{ID: 1, Name: "ada"}` names the field each value belongs to. Keys may appear in any order, and any field you omit is set to its zero value — `0`, `""`, `false`, or nil for a pointer, slice, map, channel, function or interface field. That is why `User{}` is legal and gives you an all-zero `User`. Mixing is a compile error: `User{ID: 1, "ada"}` does not build. A literal is either all keyed or all positional. One extra rule bites across package boundaries: you cannot set an unexported field of a struct that belongs to another package, and you cannot skip it either. So a positional literal of an imported struct type that has any unexported field simply does not compile — keyed is your only option there. ## What actually breaks, and how loudly The usual one-line justification for keyed literals — *"otherwise adding a field silently shifts your values"* — is not quite right, and knowing why is what separates a memorised rule from an understood one. * **A field is added.** The positional literal now has too few values, and the compiler rejects it. That is noisy, mechanical churn: every construction site in the codebase must be edited. Annoying, but safe. * **Two same-typed fields are reordered** (or one field is repurposed into another of the same type). The literal still has the right number of values, and every value still type-checks. It compiles, and every field now holds the neighbouring field's data. Nothing warns you. This is the silent failure, and it survives the test suite whenever the tests build their fixtures the same positional way. * **A field's type changes.** Usually a compile error, so it behaves like the first case. So the honest statement is: positional literals turn a struct's *declaration order* into part of its public contract, and Go gives you no way to signal that the contract changed. ## The tooling that catches it `go vet` ships a `composites` analyzer that reports composite literals of struct types **imported from another package** that use unkeyed fields. It deliberately does not flag literals of types declared in the same package, on the theory that you can see and fix those together. Since `go test` runs a subset of vet automatically, an unkeyed literal of a dependency's type tends to surface the first time somebody runs the tests. Treat that report as a real defect, not noise: it names exactly the case where the fields can move without you noticing. ## Generated code and review This matters most where literals are produced in bulk rather than typed. A code generator that emits Go source — turning a schema or a template with placeholders into `[]Config{ ... }` — is emitting hundreds of construction sites at once. If the template writes positional literals, a single reordering upstream silently rewrites every generated record; if it writes keyed literals, the worst case is a compile error in one place. The same reasoning applies to a reviewer reading a pull request: `Rule{true, false, 3}` is unreviewable at a glance, because nothing on screen says which flag is which, whereas `Rule{Enabled: true, DryRun: false, Retries: 3}` reads without opening the type. A related trick: `fmt.Printf("%#v\n", v)` prints a value in Go syntax, and for a struct it prints the **keyed** form, e.g. `main.User{ID:1, Name:"ada"}`. That output can be pasted straight into a test as a fixture, and it is a quick way to confirm that a value you built really has the fields you meant, especially when you inherited a positional literal and want to see what it actually produced. `%+v` is the lighter version, printing `{ID:1 Name:ada}` without the type name and quoting. ## When positional is acceptable Small, closed types that you own and that will never grow — a two-field coordinate pair is the standard example, and the standard library itself constructs such types positionally. Even there, the cost of keys is a few characters, and the reader benefit is immediate. The practical rule most Go teams settle on: keyed everywhere by default; positional only inside the package that declares the type, and only for types whose fields are obvious from the type name.
- Does a keyed struct literal have to mention every field?No. Any field you leave out is set to its zero value, which is why `User{}` is a valid all-zero value and `User{ID: 7}` is a common way to build a value where only one field matters. A positional literal has no such option: it must supply a value for every field, or it does not compile.
- Which unkeyed literals does go vet's composites check actually report?Only composite literals of struct types imported from another package. The reasoning is ownership: fields of a type declared in your own package move only when you move them, while a dependency can reorder its fields in any release. Same-package unkeyed literals are left alone by that analyzer.
- Can you write a positional literal for a struct from another package that has unexported fields?No. A positional literal must supply every field, and you cannot set another package's unexported fields, so it fails to compile. A keyed literal works as long as you only name exported fields; the unexported ones keep their zero values.
- Does the same keyed-versus-positional choice exist for slice and array literals?Yes, with different stakes. `[]string{"a", "b"}` is positional, while `[]string{2: "c"}` uses index keys and produces a slice of length 3 with the earlier elements zeroed. Index keys are mainly used for sparse tables; unlike struct fields, slice indices do not get renamed or reordered underneath you.
Positional is filling in an unlabelled form by row number; keyed is writing the field name next to each answer. Reprint the form with the rows swapped and only the labelled copy still says what you meant.
saying these in an interview costs you the question
- Says adding a field to a struct silently shifts a positional literal's values
- Thinks fields omitted from a keyed literal hold undefined garbage
- Claims you can mix keyed and positional elements in one literal
- Believes go vet flags every unkeyed literal, including same-package ones
- Says a positional literal may skip trailing fields