Does the Go compiler reorder struct fields to eliminate padding?
answer
- the compiler is not tidying up after you
- order in the source is order in memory
- cgo and Offsetof depend on that
- other languages made the opposite choice
- no packing pragma exists in Go
basics
~20 sNo. The gc toolchain lays fields out in declaration order, so removing padding is the programmer's job. Rust's default representation may reorder fields, which is one reason engineers arriving from it expect Go to compact a struct for them.
solid answer
~50 sGo places fields in the order you declare them and never rearranges them to close a gap. The language specification does not spell out a layout, but the guarantee that matters in practice is real: every Go toolchain lays fields out in declaration order, `unsafe.Offsetof` reflects it, cgo depends on it to line up with C, and `encoding/binary` writes fields in that order. The consequence is that padding is a property of your source text. If a struct with a `bool`, an `int64` and a `uint16` in that order wastes eight bytes, only an edit to the declaration will recover them — there is no packing pragma, no `//go:` directive and no build flag that squeezes the gaps out. That predictability is the trade: you get a layout you can reason about and interoperate with, and you own the arithmetic.
go deeper
Be ready to answer plainly that Go keeps fields in the order you wrote them, so the size you get is a consequence of your declaration.
Expect to give reasons the language keeps that guarantee — cgo, unsafe.Offsetof, encoders walking fields in order — rather than just asserting it.
Show how you stop a hand-ordered struct from regressing, with a size assertion in a test or a package-wide alignment analyzer in the build.
Own the guidance for engineers arriving from languages that reorder or allow packing, so the team neither ignores layout entirely nor tries to make memory the wire format.
## The answer and the reason The gc compiler assigns offsets by walking the fields in declaration order and padding as alignment demands. It does not sort them, and it does not reorder them under optimisation. Two structs with the same field types in different orders are genuinely different types with different layouts and potentially different sizes. The Go specification is deliberately quiet about layout — it says only that an implementation may insert padding, and it pins down the relationship between a field's address and `unsafe.Offsetof`. But no Go implementation reorders, and a great deal of working software would break if one started: - **cgo** relies on a Go struct's fields lining up with the C declaration it mirrors. - **`unsafe.Offsetof`** is used to reason about layouts and would become unpredictable. - **`encoding/binary`** and `encoding/json` walk fields in declaration order, so the output of a program would depend on the compiler's mood. - **Reviewability** — the size of a type is a function of code you can read, not of an optimiser pass you cannot see. This is a real design difference between languages. Rust's default `repr(Rust)` explicitly permits the compiler to reorder fields, and it does, which is why a Rust programmer moving to Go is often surprised that field order shows up in `unsafe.Sizeof`. C and C++ keep declaration order like Go, but they hand you `#pragma pack` and `_Alignas` to override the rules; Go has no equivalent, so the C programmer's other habit — packing a struct until its bytes are the wire format — does not port either. ## What you can control, and what you cannot You can: - **Reorder fields**, declaring them in descending order of alignment. This is the whole toolkit for removing interior padding. - **Choose narrower types.** A status that fits in a `uint8` should not be an `int`. Two `int32` timestamps beat two `int64`s if the range allows it. - **Split the type.** If a hot loop touches two fields out of twenty, pulling those two into their own struct, or holding parallel slices, cuts far more memory traffic than any amount of padding removal. You cannot: - **Force packing.** There is no way to make a struct with an `int64` field occupy a size that is not a multiple of 8. - **Ask for extra alignment.** There is no over-alignment attribute; 8 is the ceiling in the gc toolchain. - **Rely on layout as a serialisation format.** If bytes must match a fixed wire layout, encode field by field with `encoding/binary` — a translation you can read and test — rather than reinterpreting a struct's memory and hoping the padding matches. ## The porting mindset to correct An engineer arriving from a language whose compiler compacts structs will write declarations in the order the schema or the domain suggests, assume the compiler tidies up, and never look at the size. An engineer arriving from C will do the opposite: order the fields carefully, then reach for a packing pragma that does not exist, and be tempted to cast the struct's memory into a byte slice. The Go position sits between them. Order is yours, padding is automatic and unavoidable, and the bytes on the wire come from an encoder rather than from memory. The practical habit that follows is small: for a type you expect to exist in very large numbers, write down `unsafe.Sizeof` of it once, and if the number is embarrassing, reorder the declaration and leave a comment saying why the fields are grouped the way they are. For everything else, declare fields in whatever order reads best — a struct you allocate a handful of times owes nobody an optimisation.
- Since it will not reorder, what mechanism does Go give you to force a struct to be packed?None. There is no packing pragma, no compiler directive and no build flag that removes padding. Your levers are the declaration order and the widths of the field types. If the bytes must match a fixed external layout, serialise field by field with `encoding/binary` instead of trying to make the in-memory representation be the format.
- Why would the language designers avoid an optimisation that is free memory?Because layout is an interface, not an implementation detail. cgo lines Go structs up with C declarations, `unsafe.Offsetof` is defined against the real offsets, and encoders walk fields in declaration order. A compiler that silently permuted fields would make all of that compiler-version dependent, in exchange for a saving the programmer can make explicitly in one line.
- How would you stop a struct that has been carefully ordered from regressing later?Assert it. A one-line test that compares `unsafe.Sizeof(record{})` against the expected number fails the moment someone inserts a `bool` in the middle, and the failure message is a good place to explain why the order is what it is. For a whole package, the `fieldalignment` analyzer from `golang.org/x/tools` reports every struct that could be smaller.
saying these in an interview costs you the question
- Assumes the optimiser rearranges fields at higher optimisation levels
- Looks for a packed struct tag or a packing directive in Go
- Says field order is purely a style question with no runtime effect
- Plans to send a struct's raw memory as a wire format
- Confuses Rust's reordering default with Go's behaviour