In Go, how do the fmt verbs %v, %+v and %#v differ when printing a struct?
answer
- three general verbs, increasing detail
- one of them adds field names
- one prints something you could compile
- plus flag versus hash flag
- %T answers a different question entirely
basics
~10 s%v prints only a struct's field values, like {s-42 <nil>}. %+v adds the field names. %#v prints a Go-syntax literal with the package-qualified type name and quoted strings, like main.Session{ID:"s-42"}.
solid answer
~40 sAll three are fmt's general-purpose verbs for any value, and they differ in how much structure they show. `%v` is the default format: for a struct it prints the field values in braces, `{s-42 <nil>}`. `%+v` adds each field's name, `{ID:s-42 owner:<nil>}`, which is what makes it the workhorse for debug logging — you can tell which field is empty. `%#v` prints a Go-syntax representation: `main.Session{ID:"s-42", owner:(*main.User)(nil)}`, with the package-qualified type and strings quoted, so it distinguishes the string `"3"` from the number `3`. All three reach unexported fields through reflection. `%T` is separate again: it prints only the operand's dynamic type, never its contents. A nil pointer prints as `<nil>` rather than panicking.
code
go · 10 linestype Session struct {
ID string
owner *User
}
s := Session{ID: "s-42"}
fmt.Printf("%v\n", s) // {s-42 <nil>}
fmt.Printf("%+v\n", s) // {ID:s-42 owner:<nil>}
fmt.Printf("%#v\n", s) // main.Session{ID:"s-42", owner:(*main.User)(nil)}
fmt.Printf("%T\n", s) // main.Sessiongo deeper
Be ready to say what each of %v, %+v, %#v and %T prints for a small struct, and to reach for %+v in a debug log because it names the fields.
Explain the mechanics: + and # are flags on the v verb, # switches fmt to the Go-syntax path that quotes strings and consults GoStringer, and all three read unexported fields through reflection.
Show judgment about output that other people read: %+v for structured log lines, %#v when a test diff looks identical because a field is a string rather than a number, and awareness that fmt sorts map keys so printed output is stable.
Own the convention across a codebase: which verb belongs in log lines, whether printed values may leak secrets from unexported fields, and the cost of %+v on large nested structs in a hot path.
## The three general verbs Go's `fmt` package formats values under the control of *verbs* — the letters after `%` in a format string passed to `fmt.Printf`, `fmt.Sprintf` or `fmt.Fprintf`. Most verbs are type-specific (`%d` for integers, `%f` for floats, `%s` for strings). Three are general: they accept any value at all, and they differ in how much of the value's structure they reveal. ### %v — the default format `%v` prints a value the way `fmt.Print` would. For a struct it emits the field values separated by spaces inside braces, with no names: ``` {s-42 <nil>} ``` For a slice you get `[1 2 3]`, for a map `map[a:1 b:2]`, for a pointer to a struct at the top level `&{s-42 <nil>}`, and for a nil pointer, nil slice, nil map or nil interface, `<nil>`. Note that fmt never panics on a nil operand — it prints the angle-bracket form instead. ### %+v — add field names The `+` flag, applied to `v`, asks for the "plus" variant. For structs that means each field is printed as `name:value`: ``` {ID:s-42 owner:<nil>} ``` This is the verb you want in a log line or a debug inspector, because a struct with six fields printed by `%v` is a row of values you have to count off against the type declaration. `%+v` is self-describing. For non-struct types the `+` flag mostly changes nothing, though it does force `+` signs on numbers when used with numeric verbs. ### %#v — Go-syntax representation The `#` flag asks for the *Go-syntax* representation: output that looks like source code you could paste back into a program. ``` main.Session{ID:"s-42", owner:(*main.User)(nil)} ``` Three things change compared with `%+v`. The type name appears, package-qualified. Strings are quoted, so `"3"` and `3` are no longer indistinguishable, and an empty string shows as `""` rather than as nothing at all. Composite values are printed as composite literals, e.g. `[]int{1, 2, 3}` instead of `[1 2 3]`, and `map[string]int{"a":1}` instead of `map[a:1]`. `%#v` also takes a different route inside fmt: if the operand implements `fmt.GoStringer` (a `GoString() string` method), that method supplies the text. The ordinary `String() string` method is *not* consulted for `%#v`. ### %T — the type, not the value `%T` is often taught alongside these because it is the fourth thing you reach for at a debugging prompt, but it prints something different in kind: the operand's dynamic type, package-qualified. `%T` on a `Session` gives `main.Session`; on a `*Session`, `*main.Session`; on `[]string{"a"}`, `[]string`. It is the quickest way to find out what is actually inside an `any` or an interface variable, and it never looks at the contents. ## Unexported fields A common misconception is that `%v` hides unexported fields and `%+v` reveals them. In fact all three verbs print unexported fields — fmt walks the value with reflection and can read them, even though your code outside the package cannot. What you cannot do is get at them through a method or an accessor; printing is a special case. ## Practical use For a terminal inspector or a log statement, the rule of thumb most Go codebases settle on is: - `%v` when the shape is obvious and short — a duration, an ID, a small slice. - `%+v` for structs in logs and error messages: names make the output diffable and greppable. - `%#v` when you need to distinguish types or reproduce the value in a test fixture — for example when a test failure says the two values differ but `%+v` prints them identically, because one field is the string `"1"` and the other the integer `1`. - `%T` when you want to know what a value *is* rather than what it holds. ## Determinism Ranging over a Go map yields keys in a randomised order, deliberately. Printing one does not: fmt sorts map keys before emitting them, so `fmt.Printf("%v", m)` gives stable, comparable output across runs. That has been true since Go 1.12, and it is why printed maps are safe to use in golden-file tests while a hand-rolled loop over the same map is not. ## Error output when you get it wrong fmt does not fail loudly; it embeds a complaint in the output. A verb that does not apply to the operand gives `%!d(string=hi)`; too few operands give `%!d(MISSING)`; too many append `%!(EXTRA string=hi)`. Seeing a `%!` in a log line means the format string and the arguments disagree — `go vet` checks Printf-style calls and will normally catch it before the code runs.
- What does %T print for a *Session, and how is that different from %v?`%T` prints the dynamic type, `*main.Session`, and never looks inside the value. `%v` on a top-level pointer to a struct prints the pointed-at value with an ampersand, `&{s-42 <nil>}`. So `%T` answers "what is this" and `%v` answers "what does it hold".
- Is fmt's output for a map operand deterministic between runs?Yes. Since Go 1.12 fmt sorts map keys before printing, so `fmt.Printf("%v", m)` produces the same text every run. That is different from ranging over the map yourself, where the iteration order is deliberately randomised. Printed maps are therefore safe in golden-file test comparisons.
- Do these verbs print unexported struct fields?Yes, all of `%v`, `%+v` and `%#v` do — fmt walks the value with reflection and can read fields your own package-external code cannot touch. `%+v` names them, and `%#v` shows them in Go-literal form. Printing is a deliberate special case; there is no accessor equivalent.
saying these in an interview costs you the question
- Says %v hides unexported fields and %+v reveals them
- Thinks %#v calls the type's String method
- Expects field values from %T
- Claims printing a map gives a random key order
- Assumes a nil pointer field makes Printf panic