skip to content

If a type implements both error and fmt.Stringer, which method does fmt.Printf("%v", x) call?

level: seniorimportance: nice to knowfreq 28%

answer

  1. fmt asks a fixed list of questions
  2. one interface outranks all the others
  3. being an error settles it
  4. the friendlier method becomes dead code
  5. Format sees the verb and the flags

basics

~10 s

Error() wins. For the string-accepting verbs fmt checks error before fmt.Stringer, so the String method is dead code on a type that is also an error. Only fmt.Formatter takes precedence over both.

solid answer

~40 s

fmt tries a fixed sequence of interfaces on each operand. `fmt.Formatter` — `Format(f fmt.State, verb rune)` — is checked first and, if present, handles every verb itself, including `%d`. Otherwise, for `%#v` fmt looks for `fmt.GoStringer`; for `%v`, `%s`, `%q`, `%x` and `%X` it looks for `error` and then for `fmt.Stringer`, in that order. Anything else falls through to reflection-based default formatting. So a type with both `Error() string` and `String() string` always prints its `Error()` text, and the `String()` method never runs under fmt — a silent trap when someone adds a friendlier `String()` to an error type and wonders why logs are unchanged. Implementing both on one type is best avoided; if you truly need verb-sensitive output, implement `Formatter` and inspect `f.Flag('+')`, `f.Width()` and `f.Precision()`.

code

go · 7 lines
go
type StatusErr struct{ Code int }

func (e *StatusErr) Error() string { return fmt.Sprintf("status %d", e.Code) }

func (e *StatusErr) String() string { return "service unavailable" }

fmt.Printf("%v\n", &StatusErr{Code: 503}) // status 503

go deeper

for a junior

Remember the outcome rather than the machinery: if a type has Error(), that is the text fmt prints, and adding String() to it changes nothing.

for a middle

Be able to recite the order fmt tries — Formatter, then GoStringer for %#v, then error, then Stringer — and note that numeric verbs skip all of them.

for a senior

Show that you would catch the dead String method in review, pin a type's printed form with a test, and know when Formatter with fmt.State's Flag, Width and Precision earns its extra complexity.

for a principal

Set the rule that a type has exactly one printed form and that operator-facing output is a contract: changing which of these interfaces a shared type implements silently rewrites every log line it appears in.

## The order fmt uses Every operand handed to `fmt.Printf`, `fmt.Sprintf` or `fmt.Fprintf` arrives as an interface value, and fmt asks a series of questions about it before falling back to reflection: 1. **Does it implement `fmt.Formatter`?** If yes, fmt calls `Format(f fmt.State, verb rune)` and hands over completely — for *every* verb, numeric ones included. Nothing after this point is consulted. 2. **Is the verb `%#v`, and does it implement `fmt.GoStringer`?** If yes, `GoString() string` supplies the text. 3. **Is the verb one that accepts a string — `%v`, `%s`, `%q`, `%x`, `%X`?** If yes, fmt checks, in this order: - `error` (`Error() string`), - `fmt.Stringer` (`String() string`). 4. Otherwise, default formatting by reflection: struct braces, slice brackets, sorted map keys, and so on. The consequence people hit in practice is step 3's ordering: **`Error()` beats `String()`**. A type carrying both methods prints its error text everywhere fmt is involved, and the `String()` method is unreachable through fmt. ## Why this bites The typical sequence is that a service defines a status or failure type with `Error() string` so it satisfies `error`. Later someone wants nicer operator-facing output in a terminal inspector and adds a `String() string` returning a human-friendly summary. Nothing changes in the logs. There is no compile error, no vet diagnostic, no runtime marker — the new method simply never runs. The engineer then reaches for explicit calls (`log.Print(x.String())`) and ends up with two spellings of the same value drifting apart. The rule to remember and to enforce in review: **one type, one printed form.** If a type is an error, `Error()` *is* its printed form. If you need a second, richer rendering, put it on a different method with a name that says so (`Detail()`, `Summary()`), call it explicitly, and do not name it `String()`. ## fmt.Formatter, the escape hatch ```go type Formatter interface { Format(f State, verb rune) } ``` `fmt.State` gives the method everything fmt knows about the call site: - `Write([]byte) (int, error)` — State is an `io.Writer`, so you emit output by writing to it (often via `io.WriteString(f, s)`). - `Width() (int, bool)` and `Precision() (int, bool)` — the numbers from the verb, plus whether they were specified at all. - `Flag(c int) bool` — whether a flag such as `'+'`, `'#'`, `'-'`, `' '` or `'0'` was present. That is how a type offers `%v` for a compact form and `%+v` for a detailed one, or gives `%d` a meaning of its own. Because `Format` intercepts *every* verb, it must handle the ones it does not care about — the convention is to emit the standard bad-verb marker, `fmt.Fprintf(f, "%%!%c(...)", verb)`, or to fall back to default formatting of a conversion of the receiver. The cost is real: `Formatter` is more code, is easy to get wrong for unexpected verbs, and — like `String()` — must not format the receiver in a way that re-enters itself. Most types are better served by a plain `String()`. Reach for `Formatter` when the printed form genuinely differs by verb or flag: a big value with a summary and a full form, a unit-carrying number, a redacting wrapper that prints in full only under `%+v`. ## Related precedence questions - `%#v` and `GoStringer` sit *outside* the Stringer branch, so a type may have both a `String()` for readable output and a `GoString()` for a Go-literal form without either shadowing the other. - Numeric verbs never consult `error` or `Stringer` at all. `%d` on an error type with an integer underlying type prints the number. - A `Formatter` on a type that is also an `error` means `Format` handles error printing too — including when the value is printed through the `error` interface elsewhere — so that combination needs care. ## How to verify precedence in practice Write the tiny experiment rather than reasoning about it: a struct with both methods, one `fmt.Printf("%v")`, and read the output. Better still, write it as a test — the printed form of an operator-facing type is worth pinning, because a later refactor that adds or removes one of these interfaces changes every log line the type appears in, without touching a single call site.

  • Which interface outranks both error and fmt.Stringer, and what does it receive?
    `fmt.Formatter`, whose single method is `Format(f fmt.State, verb rune)`. fmt checks it first and then steps aside completely, for every verb including `%d`. `fmt.State` is an `io.Writer` that also exposes `Width()`, `Precision()` and `Flag(c int)`, so the method can render differently for `%v` and `%+v`.
  • When is implementing fmt.Formatter worth the extra code?
    When the printed form genuinely differs by verb or flag — a compact `%v` and a detailed `%+v`, a value that redacts itself unless a flag is set, or a type giving `%d` its own meaning. Otherwise prefer a plain `String()`: Formatter intercepts every verb, so you must handle the ones you did not plan for.
  • Does adding a String method to an existing error type change anything at all?
    Not through fmt, and not through `log` or anything else that formats the value — `Error()` is found first for every string-accepting verb. It only runs if someone calls it explicitly. That silence is the danger: the method looks alive in review and is unreachable in practice.

saying these in an interview costs you the question

  • Says String wins because it is more specific
  • Thinks fmt calls both methods and joins them
  • Expects a compile error for the ambiguous pair
  • Believes Formatter only affects %v
  • Claims a String method changes what log.Printf writes for an error