Formatting with fmt
Reviewed & published by Brayan K
The fmt package formats and prints values using verbs like %d, %s, and %v, giving you precise control over how numbers, strings, structs, and errors appear.
Part of the free Go course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.
What You'll Learn in This Lesson
- Use Print, Println, Printf, and Sprintf
- Format values with verbs %d %s %v %q %T
- Inspect structs with %v, %+v, and %#v
- Control width and precision (e.g. %8.3f)
- Customize output by implementing String()
- Wrap errors with Errorf and %w
1️⃣ The Print Family
There are three families. Println separates arguments with spaces and adds a newline — perfect for quick prints. Printf takes a format string full of verbs and your values, giving exact control. Sprintf is identical to Printf but returns the string instead of printing it, so you can store or pass it around. (Plain Print adds no newline and only spaces between non-string operands — a common surprise.)
package main
import "fmt"
func main() {
name := "Ada"
age := 36
// Println adds spaces between args and a trailing newline.
fmt.Println("name:", name, "age:", age)
// Printf uses a format string with VERBS (%...).
fmt.Printf("%s is %d years old\n", name, age)
// Sprintf returns the formatted string instead of printing it.
line := fmt.Sprintf("[%s|%d]", name, age)
fmt.Println(line)
// Print (no -ln) adds no newline and only spaces between
// non-string operands.
fmt.Print("a", "b", 1, 2)
fmt.Println()
}2️⃣ Verbs, Width & Precision
Verbs tell fmt how to render each value: %d integer, %s string, %f float, %v the default form, %+v a struct with field names, %#v Go-syntax, %T the type, and %q a quoted string. Add a width (%5d) to pad, a leading - to left-justify, and a precision (%.2f) for decimal places.
package main
import "fmt"
type Point struct {
X, Y int
}
func main() {
p := Point{1, 2}
fmt.Printf("%v\n", p) // {1 2} default
fmt.Printf("%+v\n", p) // {X:1 Y:2} with field names
fmt.Printf("%#v\n", p) // main.Point{X:1, Y:2} Go syntax
fmt.Printf("%T\n", p) // main.Point the type
s := "hi"
fmt.Printf("%s vs %q\n", s, s) // hi vs "hi" (%q quotes)
n := 255
fmt.Printf("dec=%d hex=%x bin=%b\n", n, n, n)
// Width and precision.
fmt.Printf("|%5d|%-5d|\n", 42, 42) // right- then left-padded
fmt.Printf("pi=%.2f width=%8.3f\n", 3.14159, 3.14159)
}3️⃣ The Stringer Interface & Errorf
If a type has a String() string method it satisfies the fmt.Stringer interface, and fmt automatically uses it for %v and %s — your own pretty format, everywhere. Separately, fmt.Errorf builds an error from a format string, and the special %w verb wraps an existing error so it can later be matched with errors.Is or unwrapped.
package main
import (
"errors"
"fmt"
)
// Implementing String() makes %v / %s use your own format.
type Temp float64
func (t Temp) String() string {
return fmt.Sprintf("%.1f°C", float64(t))
}
func main() {
t := Temp(21.5)
fmt.Println(t) // 21.5°C (Stringer kicks in)
fmt.Printf("%v\n", t) // 21.5°C
// Errorf builds an error; %w WRAPS another error so it can be
// unwrapped later with errors.Is / errors.Unwrap.
base := errors.New("disk full")
wrapped := fmt.Errorf("save failed: %w", base)
fmt.Println(wrapped) // save failed: disk full
fmt.Println(errors.Is(wrapped, base)) // true
}🎯 Your Turn
Fill in the blank to build the string "Go v1.24" with %s and %.2f.
package main
import "fmt"
func main() {
name := "Go"
version := 1.24
// Build "Go v1.24" using Sprintf:
// - %s for the name
// - %.2f for the version (two decimal places)
s := fmt.___("%s v%.2f", name, version) // 👉 Sprintf
fmt.Println(s)
// ✅ Expected output: Go v1.24
}Common Errors (and the fix)
- ❌ Output shows %!d(string=hi) — the verb doesn't match the argument type. ✅ Use %s for strings, %d for ints, or %v for anything.
- ❌ %!(EXTRA ...) at the end — you passed more arguments than verbs. ✅ Match the count, or switch to Println.
- ❌ Forgot the \n in Printf — lines run together. ✅ Printf never adds a newline; include \n yourself (or use Println).
- ❌ Used %v when you needed unwrapping — the cause is lost. ✅ Wrap with %w in fmt.Errorf so errors.Is works.
🎯 Mini-Challenge: Aligned Price Table
Print each item with the name left-justified in a 10-wide field and the price as $ with 2 decimals in a 6-wide field, so the columns line up. The verb is %-10s $%6.2f.
package main
import "fmt"
func main() {
// 🎯 MINI-CHALLENGE: print an aligned price table.
// For each item print the name left-justified in a 10-wide field
// and the price as $ with 2 decimals in a 6-wide field:
// fmt.Printf("%-10s $%6.2f\n", name, price)
//
// ✅ Expected output:
// Pen $ 1.50
// Notebook $ 3.25
// Stapler $ 12.99
items := []struct {
Name string
Price float64
}{
{"Pen", 1.5},
{"Notebook", 3.25},
{"Stapler", 12.99},
}
// your code here
}🎉 Lesson Complete!
- ✅ Println for quick prints, Printf for control, Sprintf to return a string
- ✅ Verbs: %d %s %f %v %+v %#v %T %q
- ✅ Width and precision (%8.3f, %-10s) align and round output
- ✅ Implement String() to customize how a type prints
- ✅ fmt.Errorf with %w wraps errors for later unwrapping
Practice quiz
Which Print function returns the formatted string instead of printing it?
- fmt.Println
- fmt.Printf
- fmt.Sprintf
- fmt.Print
Answer: fmt.Sprintf. Sprintf formats exactly like Printf but returns the result as a string.
What does fmt.Println("a", "b", 1) print?
- a b 1
- ab1
- a, b, 1
- ab 1
Answer: a b 1. Println puts a space between every argument and adds a trailing newline.
For a struct Point{1,2}, what does %v print?
- {X:1 Y:2}
- main.Point{X:1, Y:2}
- main.Point
- {1 2}
Answer: {1 2}. %v is the default format; for a struct it shows just the values: {1 2}.
Which verb adds the field names, printing {X:1 Y:2}?
- %v
- %+v
- %#v
- %T
Answer: %+v. %+v includes field names, the most useful for debugging structs.
What does %T print for a value?
- its type
- its address
- its Go-syntax representation
- its zero value
Answer: its type. %T prints the value's type, e.g. main.Point.
What does fmt.Printf("%d", 255) print?
- ff
- 11111111
- 255
- 0xff
Answer: 255. %d formats an integer in decimal: 255. %x gives ff and %b gives 11111111.
What does %q do to the string hi?
- prints hi
- prints "hi" (double-quoted)
- prints HI
- prints the length
Answer: prints "hi" (double-quoted). %q prints the string as a double-quoted Go literal with escaping.
What does fmt.Printf("%.2f", 3.14159) print?
- 3.14159
- 3.1
- 3.142
- 3.14
Answer: 3.14. The .2 precision rounds the float to two digits after the decimal point.
In %-10s, what does the leading minus do?
- negates the number
- left-justifies within the width
- removes padding
- right-justifies
Answer: left-justifies within the width. A leading - makes the value left-justified in the 10-wide field instead of right.
What does the %w verb in fmt.Errorf do?
- prints a warning
- formats a width
- wraps another error so it can be unwrapped
- writes to a file
Answer: wraps another error so it can be unwrapped. %w wraps an error, keeping it detectable later with errors.Is / errors.Unwrap.
Continue this course
- Previous: Runes, Bytes & UTF-8
- Next: Type Conversions & Assertions — Numeric conversions, strconv, type assertions with comma-ok, and type switches
Frequently asked questions
What's the difference between Print, Println, and Printf?
Println adds a space between every argument and a trailing newline — great for quick debugging. Print adds no newline and only inserts spaces between operands when neither is a string. Printf takes a format string with verbs (%d, %s, ...) and gives you full control over layout, width, and precision. Their S-prefixed cousins (Sprintln, Sprintf) return the result as a string instead of printing it.
What is the difference between %v, %+v, and %#v?
%v is the default format. For a struct it prints just the values, like {1 2}. %+v adds the field names: {X:1 Y:2}, which is the most useful for debugging. %#v prints a Go-syntax representation you could paste back into code: main.Point{X:1, Y:2}. There's also %T, which prints the value's type rather than its contents.
When should I use %q instead of %s?
%s prints a string as-is. %q prints it as a double-quoted Go string literal, with escaping for special characters — so a tab becomes and the whole thing is wrapped in quotes. %q is ideal when you want to see exactly where a string begins and ends, or to reveal hidden whitespace while debugging.
How do width and precision work in a verb like %8.3f?
The number before the dot is the minimum field width (the value is padded with spaces to at least that many characters); a leading minus like %-8s left-justifies instead of right-justifies. The number after the dot is the precision — for floats it's the number of digits after the decimal point, and for strings it's the maximum number of characters to print.
What does the %w verb in fmt.Errorf do?
%w wraps another error inside the new one, preserving a chain. The wrapped error can later be recovered with errors.Unwrap or matched with errors.Is and errors.As. Use %w (not %v) when you want callers to be able to inspect the underlying cause; use %v when you only want the text and no unwrapping.