AlgoMaster Logo

String Formatting (fmt verbs)

Last Updated: May 17, 2026

13 min read

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.

The fmt Family of Functions

Before 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.

FunctionDestinationReturns
fmt.Printfstdoutbytes written, error
fmt.Printlnstdout (auto newline, auto spaces)bytes written, error
fmt.Sprintfa new stringthe string
fmt.Fprintfany io.Writer (file, network, buffer)bytes written, error
fmt.Errorfa new error valuethe 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.

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:

General Verbs: %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.

VerbMeaningExample output for a struct
%vDefault format{Mouse 24.99 17}
%+vDefault format with field names{Name:Mouse Price:24.99 Stock:17}
%#vGo syntax representationmain.Product{Name:"Mouse", Price:24.99, Stock:17}
%TType of the valuemain.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.

Integer Verbs

For integer values, the verb chooses the base.

VerbBaseExample for 255
%dDecimal (base 10)255
%bBinary (base 2)11111111
%oOctal (base 8)377
%xHex, lowercaseff
%XHex, uppercaseFF
%cUnicode character for the runeÿ
%UUnicode code pointU+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.

Float Verbs

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.

VerbStyleExample for 3.14159
%fDecimal, default 6 decimal places3.141590
%.2fDecimal, precision 23.14
%eScientific, lowercase e3.141590e+00
%EScientific, uppercase E3.141590E+00
%gCompact: %e for big exponents, else %f3.14159
%GSame as %g but uppercase E3.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.

String Verbs

For strings (and byte slices) the verb chooses how the content is escaped, if at all.

VerbMeaningExample for "héllo\n"
%sThe plain stringhéllo (then newline)
%qDouble-quoted with Go escape sequences"héllo\n"
%xBytes as lowercase hex68c3a96c6c6f0a
%XBytes as uppercase hex68C3A96C6C6F0A

%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.

Boolean Verb

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.

Pointer Verb

%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.

Flags, Width, and Precision

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

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.

Padding with Zeros

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.

Precision for Strings

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.

Precision for Floats

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.

Sign Flags: + and Space

By 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.

The # Flag

The # flag asks for an "alternate" format. It's verb-specific:

Verb# adds
%#x0x prefix
%#X0X prefix
%#o0 prefix
%#vGo-syntax format (covered above)
%#qBacktick-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.

Argument Indexing

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.

The Stringer Interface

When 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.
  • If your 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.

The %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.

Comparison with strconv

The 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:

NeedPreferWhy
Convert one integer to a stringstrconv.Itoa(n)No reflection, single conversion
Convert one float to a string with precisionstrconv.FormatFloat(f, 'f', 2, 64)Faster than Sprintf
Parse a string to an integerstrconv.Atoi(s)Returns the value and an error
Build a multi-piece formatted stringfmt.Sprintf(...)One call, readable layout
Format with width, padding, or precision rulesfmt.Sprintf(...)strconv has no layout features
Print a struct or interface valuefmt.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.

Putting It Together: A Receipt Printer

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.

Summary

  • 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.
  • Integer verbs cover the common bases: %d (decimal), %b (binary), %o (octal), %x and %X (hex). %c renders a rune as a character, %U as a Unicode code point.
  • Float verbs are %f (fixed), %e (scientific), and %g (compact). Precision controls decimals, as in %.2f for currency.
  • String verbs are %s (plain), %q (Go-quoted with escapes), and %x (bytes as hex). Use %q in error messages so whitespace and control characters stay visible.
  • Width sets a minimum: %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.
  • The 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.
  • For single conversions, 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.