skip to content

In a Go fmt verb such as %8.2f, what do the width and the precision each control?

level: middleimportance: should knowfreq 42%

answer

  1. two numbers around one dot
  2. the first one only ever pads
  3. the second one changes meaning per verb
  4. strings are the ones that get cut
  5. a star reads it from an argument

basics

~10 s

Width is the minimum output size, padded with spaces, so %8.2f never prints fewer than 8 characters. Precision follows the dot and is verb-dependent: decimal places for %f, a truncation limit for %s.

solid answer

~40 s

In `%[flags][width].[precision][verb]`, width is the minimum number of runes written — fmt pads with spaces on the left to reach it, and never truncates to it. Precision means different things per verb: for `%f` and `%e` it is the number of digits after the point, for `%g` the number of significant digits, for `%s` and `%q` it is a limit on how much of the *input* is used, so `%.3s` of `"gopher"` gives `gop`, and for integers it sets a minimum digit count padded with leading zeros. Flags modify padding: `-` left-justifies, `0` pads numbers with zeros instead of spaces, `+` always shows a sign. Either number can be `*`, which takes the value from the next operand, so `fmt.Printf("%*d", 5, 42)` pads to width 5 at runtime.

code

go · 5 lines
go
fmt.Printf("|%8.2f|\n", 3.14159) // |    3.14|
fmt.Printf("|%-8s|\n", "go")     // |go      |
fmt.Printf("|%.3s|\n", "gopher") // |gop|
fmt.Printf("|%06.2f|\n", 3.1415) // |003.14|
fmt.Printf("|%*d|\n", 5, 42)     // |   42|

go deeper

for a junior

Know the %[width].[precision]verb shape and be able to print a float to two decimal places with %.2f and pad a column with %-10s.

for a middle

Explain that width is a minimum measured in runes while precision's meaning is verb-dependent — decimals for %f, significant digits for %g, input truncation for %s, minimum digits for %d.

for a senior

Demonstrate control over operator-facing output: capping untrusted fields with a precision, quoting them with %q so newlines cannot forge log lines, and computing widths with * rather than by string concatenation.

for a principal

Decide the house format for machine-read output — fixed columns versus structured records — and keep go vet's printf analyzer in the build so format-argument drift is caught before it reaches a dashboard.

## Anatomy of a verb A fmt verb has the shape ``` %[flags][width][.precision]verb ``` and every part except the verb letter is optional. Written out, `%-08.3f` means: left-justify, zero-pad, minimum width 8, three digits after the point, floating-point decimal. (In practice `-` and `0` together are contradictory and `-` wins.) ## Width Width is a **minimum**. fmt pads the formatted form with spaces until it reaches that size, and if the value is already wider, nothing is cut. This is the single most common misunderstanding: `%3s` applied to `"gopher"` prints `gopher`, not `gop`. Width is what lines up columns in a terminal inspector; it is never a length limit. For most values width is counted in **runes**, not bytes, so a column of names containing non-ASCII text still lines up under `%-12s` — as long as each rune renders one cell wide, which is not guaranteed for East Asian characters or combining marks. Padding is on the left by default. The `-` flag pads on the right instead (left-justification), and the `0` flag pads numeric verbs with leading zeros, placed after any sign: `%+06.2f` of `3.1` yields `+03.10`. ## Precision Precision is everything after the dot, and unlike width its meaning is decided by the verb: - **`%f`, `%e`, `%E`** — the number of digits after the decimal point. `%.2f` of `3.14159` gives `3.14` (rounded, not truncated). The default is 6. - **`%g`, `%G`** — the maximum number of *significant* digits. The special value `-1`, available through `strconv`, means "the shortest representation that round-trips", which is also what `%v` uses for floats. - **`%s`, `%q`, `%v` on strings and byte slices** — a limit on the input consumed, i.e. truncation. `%.3s` of `"gopher"` is `gop`. This is the only place precision shortens output rather than shaping numbers, and it is genuinely useful for capping a user-supplied field in a log line. - **Integers (`%d`, `%x`, ...)** — a minimum number of digits, padded with leading zeros. `%.5d` of `42` gives `00042`. A dot with no number means precision zero: `%.f` of `3.7` gives `4`. ## Combining the two `%8.2f` on `3.14159` produces ` 3.14` — precision first shortens the number to four characters, then width pads it out to eight. Reading it in that order makes the combination easy to predict. `%-10.4s` on a long string truncates to four runes and then pads to ten on the right. ## Taking width or precision from a variable Either number may be written as `*`, which consumes an `int` operand ahead of the value: ```go w := 5 fmt.Printf("%*d\n", w, 42) // " 42" fmt.Printf("%.*f\n", 3, 2.5) // "2.500" ``` This is how you build a table whose column width is computed from the data at runtime, instead of assembling a format string by concatenation. ## %q, and why it belongs in this discussion `%q` prints a double-quoted Go string literal with escapes applied: a value containing a newline prints as `"a\nb"` on one line rather than breaking your log format, and a trailing space becomes visible because the closing quote is right after it. For anything user-supplied — a header value, a filename, a parsed token — `%q` is the safer verb, because `%s` of an empty string is indistinguishable from a missing field and `%s` of hostile text can inject line breaks into a log stream. Precision applies to `%q` the same way as to `%s`, capping the input consumed. ## Getting it wrong fmt reports format errors in the output stream rather than failing: an inapplicable verb gives `%!d(string=hi)`, a missing operand `%!d(MISSING)`, a stray operand `%!(EXTRA int=3)`, and a bad width or precision indicator `%!(BADWIDTH)` or `%!(BADPREC)`. Since `go vet` includes a printf analyzer that checks format strings against arguments in Printf-style calls, most of these are caught before the program ever runs — which is why keeping vet in the build is worth more than memorising the markers.

  • How do you set a column width computed at runtime rather than hard-coding it?
    Use `*` in place of the number and pass the width as an `int` operand before the value: `fmt.Printf("%*d", w, 42)`. Precision works the same way with `%.*f`. This avoids building format strings by concatenation, which defeats the `go vet` printf checks.
  • Does width ever truncate a value that is too long?
    No. Width is a minimum — `%3s` of `"gopher"` still prints all six characters. Only precision truncates, and only for strings and byte slices: `%.3s` gives `gop`. Confusing the two is why terminal tables built with width alone still break their columns on long values.
  • Why prefer %q over %s for user-supplied text in a log line?
    `%q` emits a double-quoted Go literal with escapes, so an embedded newline prints as `\n` instead of splitting your log record, trailing whitespace becomes visible, and an empty value shows as `""` rather than as nothing. It makes hostile or surprising input obvious instead of silently malformed.

saying these in an interview costs you the question

  • Says width truncates values that are too long
  • Thinks precision means decimal places for every verb
  • Believes %.3s rounds instead of cutting the string
  • Builds format strings by concatenation instead of using *
  • Assumes a bad verb makes Printf return an error