debug a problem involving Fmt Sprintf
Fast recall
Quick revision
Core answer
Practical understanding of Fmt Sprintf.
Repeating a definition without saying when to use it.
Give one trade-off and say how you would test it.
Interview-ready guide
Interview answer
One format mini-language, four destinations:
fmt.Printf("Hi %s\n", name) // → stdout
s := fmt.Sprintf("Hi %s", name) // → string (returned)
fmt.Fprintf(w, "Hi %s", name) // → any io.Writer: file, buffer, network
err := fmt.Errorf("Hi %s: %w", name, cause) // → error valueThe verbs are identical across all four — %s, %d, %v, %q, %T — learn once, use everywhere.
The choosing rule is the destination: console output → Printf; building a string for later use (API messages, descriptions) → Sprintf; writing to anything that isn't stdout (log files, HTTP responses, bytes.Buffer, sockets) → Fprintf (that's the io.Writer interface — the same one bytes.Buffer, files, and network connections implement); creating an error with context → Errorf.
Errorf's special verb: %w. In Errorf ONLY, %w embeds the wrapped error and links the Unwrap chain (errors.Is/As traverse it). In Sprintf, %w is invalid (a placeholder mishap — it prints %!w(...)). That single divergence is why Errorf isn't just 'Sprintf that returns an error' — it's the errors-package integration point.
The perf note that carries over from the Sprintf topic: all four parse the format at runtime — the cost profile is shared. Printf to a buffered writer, Sprintf in hot loops — the same discipline applies per destination.
debug a problem involving Fmt Sprintf
Direct Answer
All share the format verbs. Printf → stdout. Sprintf → returns a string. Fprintf → any io.Writer. Errorf → returns an error (and its %w wraps). Choose by DESTINATION.
Deep Dive & Architecture
The Writer abstraction behind Fprintf is the whole story of why four functions exist at all: fmt prints to io.Writer (the two-word interface: Write([]byte) (int, error)). Printf is literally Fprintf(os.Stdout, ...) — a convenience binding. Sprintf is Fprintf into a private buffer whose String() is returned. The destination is pluggable — which is how fmt composes with the entire IO ecosystem: a bytes.Buffer (capture for testing), a file (logging), a hash (the md5 of the formatted form), a network conn — one formatting engine, any sink.
Errorf's %w, mechanically: the format engine in Errorf scans for %w; on finding it, the wrapped error is stored in a wrapError struct alongside the formatted message, and that struct implements Unwrap() → the cause. errors.Is/As walk that chain. %v would embed the cause's TEXT in the message string — no link, no chain. The one-verb divergence is the entire errors-as-values architecture's integration with formatting — which is why 'use %w not %v when wrapping' is the rule (the errors topic covers the semantics; here the point is the FORMAT engine's role).
The testing idiom worth naming: capturing Fprintf output for assertions — write to bytes.Buffer, format into it, assert buf.String(). Because Sprintf also exists, the choice is style: Sprintf for one-shot expected strings, Fprintf-to-buffer when emulating a writer-driven component (a logger that takes io.Writer — test it with a buffer; the same code path as production). That testability is io.Writer's second dividend — the first was composability.
Common Mistakes & Pitfalls
Repeating a definition without saying when to use it.
Check your understanding
Answer these without looking back at the article.
- What is Fmt Sprintf, and when would you use it?
- When would you use Fmt Sprintf in a real project?
- How would you compare Fmt Sprintf with an alternative?
Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.