Last Updated: May 17, 2026
Almost every program needs to turn data into readable text: prices into receipts, errors into log lines, structs into debug output. Go's fmt package is the workhorse for this. It uses format verbs like %d, %s, and %v to describe how each argument should be rendered, and a small family of functions (Printf, Sprintf, Fprintf) to send the result to stdout, a string, or any writer. This chapter is the reference: every verb you'll commonly use, every flag worth knowing, and the rules for picking the right function for the job.
fmt Family of FunctionsBefore the verbs, you need to know where the formatted output goes. fmt exposes the same formatting engine through several functions; they differ only in their destination.
| Function | Destination | Returns |
|---|---|---|
fmt.Printf | stdout | bytes written, error |
fmt.Println | stdout (auto newline, auto spaces) | bytes written, error |
fmt.Sprintf | a new string | the string |
fmt.Fprintf | any io.Writer (file, network, buffer) | bytes written, error |
fmt.Errorf | a new error value | the error |
Printf is what you reach for when you want formatted output on the terminal:
Printf doesn't add a newline, so the format string ends with \n here. That's a recurring gotcha: forget the \n and successive Printf calls run together on one line.
Println is a simpler cousin. No verbs, no format string. It prints each argument with the default formatting, separates them with spaces, and appends a newline:
Use Println for quick prints. Use Printf whenever you care about precision, alignment, or how values are rendered.
Sprintf is the same engine as Printf, but it returns the formatted string instead of writing it anywhere. It's the right tool whenever you want to build a string from data:
You'll see Sprintf constantly in real Go code: building log messages, constructing keys for maps, assembling email subjects, formatting filenames. Anywhere a string needs to be assembled from parts, Sprintf is idiomatic.
Cost: Sprintf allocates a new string every call and uses reflection internally to inspect each argument. For one-off formatting that's fine. Inside a hot loop, it can dominate runtime. For repeated string building, strings.Builder is much faster, and for single numeric conversions, strconv skips reflection entirely.
Fprintf adds an io.Writer parameter as its first argument. The writer can be a file, a network connection, an os.Stderr for error output, or any type that implements Write([]byte) (int, error):
Fprintf is how you write formatted output to anywhere that isn't stdout. Logs, HTTP responses, files, byte buffers, all of them satisfy io.Writer, and Fprintf works on any of them.
Errorf is the same idea applied to errors. It builds a formatted message and returns it as an error value. It also supports a special %w verb for wrapping another error. For now, treat Errorf as Sprintf that returns an error:
%v, %+v, %#v, %T, %%These verbs work on any value type. They're the ones you'll use when you don't care about exact formatting, just want to see what's there.
| Verb | Meaning | Example output for a struct |
|---|---|---|
%v | Default format | {Mouse 24.99 17} |
%+v | Default format with field names | {Name:Mouse Price:24.99 Stock:17} |
%#v | Go syntax representation | main.Product{Name:"Mouse", Price:24.99, Stock:17} |
%T | Type of the value | main.Product |
%% | A literal % character | % |
%v is the everyday verb. It uses each type's default formatting: integers as base 10, floats with whatever precision is needed, strings as themselves, structs as space-separated field values inside braces:
%v is the safest fallback verb. If you're not sure what type something is, or if you'll only print it for debugging, %v always works.
%+v adds field names to struct output. This is the verb you want for debug logs, because the output becomes self-describing:
When a struct has many fields, %v becomes hard to read because the values aren't labeled. %+v solves that. Most Go developers use %+v for log lines that include structs:
%#v goes one step further and prints a Go-syntax representation. The output is something you could paste back into source code:
The package path and type name appear in front, and strings are quoted. %#v is most useful when you want to copy a value out of a debug log and reproduce it as a literal in code or in a test case.
%T prints the type, not the value. It's how you ask "what is this thing, really?", especially when you're holding an any and want to know what concrete type is inside:
%T is invaluable when debugging interface values. The variable's static type is any, but %T reveals the dynamic type.
%% is the escape for a literal %. You need this whenever you want a percent sign in your output, for example when formatting a discount:
A single % in a format string is the start of a verb. To get a literal % through to the output, you double it.
For integer values, the verb chooses the base.
| Verb | Base | Example for 255 |
|---|---|---|
%d | Decimal (base 10) | 255 |
%b | Binary (base 2) | 11111111 |
%o | Octal (base 8) | 377 |
%x | Hex, lowercase | ff |
%X | Hex, uppercase | FF |
%c | Unicode character for the rune | ÿ |
%U | Unicode code point | U+00FF |
%d is by far the most common. Anything that's an int, int32, int64, uint, or one of their cousins prints fine with %d:
The other bases come up in specific situations. %x is the right choice for hex IDs, file hashes, or any case where you'd write hex in source code. %b is occasionally useful for showing bit flags. %o is rare outside of Unix file permissions:
%c converts an integer to its Unicode character. This is useful when you have a rune value and want to print the actual glyph:
A rune is just an alias for int32, so the same value can be printed as a character with %c, as a number with %d, or as a Unicode code point with %U.
Floats have a few rendering choices: fixed decimal, scientific, or "whichever is more compact". Most real code uses %f with a precision specifier like %.2f.
| Verb | Style | Example for 3.14159 |
|---|---|---|
%f | Decimal, default 6 decimal places | 3.141590 |
%.2f | Decimal, precision 2 | 3.14 |
%e | Scientific, lowercase e | 3.141590e+00 |
%E | Scientific, uppercase E | 3.141590E+00 |
%g | Compact: %e for big exponents, else %f | 3.14159 |
%G | Same as %g but uppercase E | 3.14159 |
Plain %f defaults to six decimal places, which is almost never what you want for prices. The precision modifier %.Nf lets you pick how many decimals:
%.2f is the default choice for currency. It rounds (not truncates), so 2.06175 shown to 4 decimals becomes 2.0618.
%e puts the value in scientific notation. It's useful for very large or very small numbers where %f would be unreadable:
That last line shows why %f isn't always a good default: with the default 6 decimals, 0.0000001 rounds down to all zeros.
%g picks the more compact of %e and %f for you. It uses scientific notation when the exponent gets large, and fixed-point otherwise. It also drops trailing zeros:
%g is the verb you want when you don't know the magnitude of a value in advance and just want a sensible rendering. For prices and currency, stick with %.2f for predictable two-decimal output.
Cost: %f and friends use reflection to inspect the value's type. For one number per log line that's invisible. For millions of float prints in a tight loop, strconv.FormatFloat is several times faster because it skips the reflection step.
For strings (and byte slices) the verb chooses how the content is escaped, if at all.
| Verb | Meaning | Example for "héllo\n" |
|---|---|---|
%s | The plain string | héllo (then newline) |
%q | Double-quoted with Go escape sequences | "héllo\n" |
%x | Bytes as lowercase hex | 68c3a96c6c6f0a |
%X | Bytes as uppercase hex | 68C3A96C6C6F0A |
%s is the default. It prints the string verbatim. If the string contains control characters like \n or \t, they pass through unchanged:
%q wraps the string in double quotes and escapes anything that isn't printable ASCII. It's the verb to use when you want the output to make the string boundaries obvious, especially in error messages:
Notice how %s lets the embedded newline through (you see a blank line after café), while %q shows \n as the literal two characters. This is gold for error messages, because you can tell the difference between "foo" and "foo " (with a trailing space) at a glance.
%q is also the right verb when you're reporting a user-supplied value that might contain whitespace or weird characters:
Without %q, the leading space in " Alice" would have been impossible to see in the error message.
%x on a string or []byte produces the hex representation of its bytes. This is the standard way to display hashes, IDs, or other binary blobs:
(The exact hash depends on the input, but the point is the formatting.) %x works on any byte sequence: a []byte, a [N]byte array, or a string. Each byte becomes two hex characters.
There's just one: %t, which prints true or false.
%v works on booleans too and produces the same output, so you'll see both in the wild. Use %t when you want to make the intent explicit.
%p prints a pointer's address in hex with a leading 0x. It's almost only useful for debugging pointer identity, that is, asking "are these two variables pointing at the same place?":
a and b print the same address because they point at the same Product. c points at a different Product with the same content. You'll see %p in tests that verify two values are the same instance, not just equal.
Verbs accept modifiers between the % and the verb letter. The shape is:
All four parts are optional, except the % and the verb itself. The combinations are where fmt gets powerful, especially for tabular output.
Width sets a minimum number of characters. If the value is shorter, it's padded with spaces. If it's longer, the width is ignored (no truncation):
Default alignment is right. The - flag flips it to left:
Left-aligned strings (%-Ns) and right-aligned numbers (%Nd, %N.Mf) is the usual combination for tabular output. Here's a small price table:
The product column is left-aligned to width 20, the price is right-aligned to width 8 with two decimal places, and the stock column is right-aligned to width 5. The columns line up cleanly because every row uses the same format string. This is the kind of output that's tedious to produce with string concatenation but trivial with Printf.
The 0 flag pads numbers with leading zeros instead of spaces. It's the right choice for things like order IDs that should always render at a fixed width:
%06d means "minimum width 6, pad with zeros". The 0 flag is ignored if the value already exceeds the width or if - (left-align) is also set, because zero-padding a left-aligned number doesn't make visual sense.
For strings, .N truncates to a maximum of N characters. This is the only situation where width and precision really mean different things, width is a minimum, precision is a maximum:
Width plus precision is how you make a column that's wide enough to align and short enough not to blow past its boundary. For long product names in narrow columns, %.20s cuts them at 20 characters.
For floats, .N is the number of digits after the decimal point. You've already seen this with %.2f. Combine it with width to right-align prices in a column:
Width 8, precision 2. The decimal point and the two trailing digits count toward the width, so the widest entry (1000.00, 7 characters) gets one space of padding and the narrowest (0.99, 4 characters) gets four.
+ and SpaceBy default, Printf only prints a sign for negative numbers. The + flag forces a sign for positives too. A space (% d) is similar but uses a space for non-negatives:
%+d is handy for delta values like balance changes or score adjustments, where you want the reader to see the sign even when the number is positive.
# FlagThe # flag asks for an "alternate" format. It's verb-specific:
| Verb | # adds |
|---|---|
%#x | 0x prefix |
%#X | 0X prefix |
%#o | 0 prefix |
%#v | Go-syntax format (covered above) |
%#q | Backtick-quoted if possible |
%#x is the verb you want for hex IDs that should look like Go source. Without the flag, %x produces just ff, which is fine for hashes but loses the "this is a hex number" cue for an audience used to reading code.
By default, fmt consumes arguments in order, one per verb. You can break that with %[N]verb, which means "use argument N for this verb". The most common use is to reuse the same argument in multiple places:
The first %d consumes n. The %[1]b and %[1]x both say "use argument 1 again". Without indexing, you'd have to pass n three times.
Indexing also lets you reorder arguments without changing the function signature, which matters for translation strings where word order varies between languages:
Argument indexing is rare in everyday Go, but it's the only sane way to handle the "use this twice" or "swap order" cases.
Stringer InterfaceWhen you print a value with %v or %s, fmt checks whether the value has a String() string method. If it does, fmt calls it and uses the result. This is the Stringer interface, defined in the fmt package:
Implementing Stringer lets you control how your type renders without changing every print call:
Without String(), the output would have been status: 0, status: 1, and so on, because OrderStatus is an int underneath. With String(), every print of an OrderStatus value renders as text, automatically.
A few rules worth knowing:
%v and %s both call String(). %d, %x, and other type-specific verbs do not.%T shows the original type, not the string. %T of StatusShipped is main.OrderStatus, not string.String() method itself uses %v or %s on the same value, you'll get infinite recursion. Use a different verb (or convert to the underlying type) inside String().Here's an example of the recursion trap and the fix:
What's wrong with this code?
%v on a Price calls p.String(), which calls Sprintf with %v on a Price, which calls p.String(), and so on until the stack overflows. The fix is to use a type-specific verb or convert to the underlying type:
Fix:
%.2f is a float-specific verb and doesn't call String(). Converting to float64 also breaks the cycle because float64 doesn't have a String() method.
There's a related interface, fmt.Formatter, that lets a type customize %v, %+v, %#v, and other verbs independently. It's a more advanced hook and rarely needed in application code. Most of the time, Stringer is enough. You'll see Formatter mostly in standard library types like time.Time and big.Int, which want different output for different verbs.
Cost: every %v print of a Stringer involves a method call and a string allocation inside String(). Usually negligible, but a Sprintf that builds a giant string from a slice of Stringer values can do a lot of work per call. Inside a tight loop, consider building the string directly.
%w Verb (Brief Mention)The %w verb is special and only valid inside fmt.Errorf. It wraps an error so that callers can later inspect the chain with errors.Is and errors.Unwrap:
The reason it's mentioned here is so you know %w exists and that it's intentionally not part of Printf or Sprintf. Using %w with anything other than Errorf is a runtime error: the verb prints, but the value still won't be wrapped.
strconvThe strconv package does narrow type conversions: int to string, float to string, parse string to int, and so on. fmt.Sprintf can do all of those things and more, but with a performance cost. Here's the comparison:
| Need | Prefer | Why |
|---|---|---|
| Convert one integer to a string | strconv.Itoa(n) | No reflection, single conversion |
| Convert one float to a string with precision | strconv.FormatFloat(f, 'f', 2, 64) | Faster than Sprintf |
| Parse a string to an integer | strconv.Atoi(s) | Returns the value and an error |
| Build a multi-piece formatted string | fmt.Sprintf(...) | One call, readable layout |
| Format with width, padding, or precision rules | fmt.Sprintf(...) | strconv has no layout features |
| Print a struct or interface value | fmt.Sprintf("%+v", v) | strconv only handles scalars |
A small example showing both:
For the first two cases, strconv.Itoa is the right choice; it's a focused tool. For the third, where width and surrounding text matter, Sprintf is the right choice.
Cost: in benchmarks, strconv.Itoa(n) is several times faster than fmt.Sprintf("%d", n) because Sprintf parses the format string and uses reflection on each argument. For a single conversion per request, the difference is invisible. In a hot loop producing thousands of lines, it adds up.
Here's a small program that pulls together the verbs and flags you've seen. It prints an order receipt with aligned columns, a formatted total, and a status line that uses a Stringer:
Three things to notice. First, the column alignment comes entirely from format strings; there's no manual padding. Second, Order #%06d pads the order ID with zeros to width 6 for a consistent receipt look. Third, %v on order.Status calls the Stringer method automatically, so the integer constant prints as shipped instead of 1.
Printf writes formatted output to stdout, Sprintf returns it as a string, Fprintf writes it to any io.Writer, and Errorf builds a formatted error value.%v is the default verb and works on any type. %+v adds field names to structs, and %#v produces a Go-syntax representation. %T prints the type.%d (decimal), %b (binary), %o (octal), %x and %X (hex). %c renders a rune as a character, %U as a Unicode code point.%f (fixed), %e (scientific), and %g (compact). Precision controls decimals, as in %.2f for currency.%s (plain), %q (Go-quoted with escapes), and %x (bytes as hex). Use %q in error messages so whitespace and control characters stay visible.%5d pads to at least 5 characters. The - flag left-aligns, and 0 pads numbers with zeros. Precision for strings (%.5s) is a maximum, not a minimum.Stringer interface (String() string method) lets a custom type control how %v and %s render it. Don't use %v or %s inside your own String() method or you'll recurse.%w is only valid in Errorf. It wraps an underlying error so callers can inspect it with errors.Is later.strconv is faster than Sprintf because it skips reflection. For layout and combined values, Sprintf is the right tool.The next chapter, Byte Slices and Strings, looks at how Go represents strings under the hood, the relationship between string and []byte, and the cost of converting between them.