Last Updated: May 22, 2026
The fmt package handles almost all the printing and reading you'll do in early Go programs. You've already seen fmt.Println in earlier work; this lesson covers the full family of print and scan functions, the format verbs that control what comes out, and the rules for reading user input safely.
Three functions cover most of your printing needs, and the differences between them are worth pinning down once. They all write to standard output, but they treat spaces, newlines, and formatting differently.
Look at the first two lines. fmt.Print doesn't add spaces between its string arguments and doesn't end with a newline. That's why OrderConfirmed runs together. You're responsible for any whitespace and line breaks yourself.
fmt.Println is the opposite. It inserts a single space between arguments and tacks on a newline at the end. It's what you use when you just want to see a value while debugging. There's one wrinkle though: Println adds spaces between any two arguments, even if they're both strings. So fmt.Println("Total:", 49.99) prints Total: 49.99, not Total:49.99.
fmt.Printf gives you full control. The first argument is a format string with verbs (the % codes) that the remaining arguments fill in. It does not add a trailing newline, so you almost always end the format string with \n.
A more realistic example showing when each one is useful:
Use Print when you're building output piece by piece on the same line (progress dots, partial prompts). Use Println when you just want a value printed with default formatting and a newline. Use Printf when the exact layout matters: currency, padding, columns, anything where precision counts.
Sometimes you don't want to print anything yet. You want to format a value into a string and use it later, maybe to store it in a variable, return it from a function, or pass it to another function. That's what fmt.Sprintf does. The leading S stands for "string".
Sprintf takes the same format string and verbs as Printf, but instead of writing to the screen, it returns a string. This is how you build labels, log messages, error messages, file names, and any other text that needs to be shaped from multiple values.
Cost: fmt.Sprintf allocates a new string every time it's called. For a few calls that's nothing, but inside a tight loop that runs millions of times it adds up. When you're concatenating in a hot loop, use strings.Builder instead.
There's also fmt.Errorf, which works just like Sprintf but returns an error value instead of a string. You'll see it like this:
For now, just know Errorf exists and uses the same format verbs.
The % symbols in a format string are called verbs. Each verb tells Printf how to interpret the next argument. Picking the right verb is the difference between 49.989999999999995 and $49.99.
Here's a reference table of the verbs you'll use most:
| Verb | Meaning | Example Argument | Example Output |
|---|---|---|---|
%v | Default format for any value | Product{Name: "Pen"} | {Pen} |
%+v | Struct with field names | Product{Name: "Pen"} | {Name:Pen} |
%#v | Go syntax representation | []int{1, 2} | []int{1, 2} |
%T | Type of the value | 49.99 | float64 |
%d | Integer in base 10 | 42 | 42 |
%b | Integer in binary | 5 | 101 |
%o | Integer in octal | 8 | 10 |
%x | Integer in lowercase hex | 255 | ff |
%X | Integer in uppercase hex | 255 | FF |
%f | Floating point, default precision | 3.14 | 3.140000 |
%.2f | Float with 2 decimal places | 3.14159 | 3.14 |
%s | String | "Pen" | Pen |
%q | Quoted string (with escapes) | "Pen" | "Pen" |
%t | Boolean | true | true |
%p | Pointer address | &x | 0xc0000140a0 |
%% | A literal % character | (none) | % |
A quick tour of the three %v variants, because they confuse people:
%v prints just the values. %+v adds field names, which is what you want when debugging a struct with several fields. %#v prints the value in valid Go syntax, useful when you want to paste it back into source code. %T skips the value entirely and tells you the type, which is handy when a variable is doing something unexpected.
The %d family covers integers in different number systems. %d is decimal (the everyday case). %b, %o, %x, and %X give you binary, octal, lowercase hex, and uppercase hex. You'll mostly use %d; the others come up in low-level work or when printing things like color codes.
For floats, the most useful one is %.2f, which forces two decimal places. That's what you'll use for prices.
Note that %.0f rounds. 19.5 becomes 20, not 19. Go uses banker's rounding (round half to even), so 2.5 also rounds to 2 and 3.5 rounds to 4. This rarely matters, but it's worth knowing if you're surprised by a result.
%q is the quoted-string verb. It wraps a string in double quotes and escapes any special characters inside it. This is useful when you want to log a string and you need to see exactly what's in it, including any hidden whitespace.
The third line is the giveaway. With %s you'd see an actual tab. With %q you see \t, so you can tell the string has a tab in it.
Verbs accept an optional width (minimum number of characters) and precision (decimal places for floats, max characters for strings). This is how you build aligned columns and zero-padded numbers.
The pattern is %[flags][width][.precision]verb. A few examples make it concrete:
Walking through these:
%5d reserves 5 characters and right-aligns the number (default).%-5d reserves 5 characters but left-aligns (the - flag flips the alignment).%05d reserves 5 characters and pads with zeros on the left instead of spaces.%10s right-aligns the string in a 10-character field.%-10s left-aligns the string in a 10-character field. This is the one you'll use for product tables.%8.2f reserves 8 characters total and forces 2 decimal places. The number 3.14 takes 4 characters, leaving 4 spaces of left padding.Aligned columns are where these flags pay off. Here's a product table that uses them:
Each column lines up because every row uses the same widths. The product name is left-aligned in 20 characters (%-20s), the price is right-aligned in 7 characters with 2 decimals (%7.2f), and the stock count is right-aligned in 6 characters (%6d).
The fmt package can read user input too. The three main functions are Scan, Scanln, and Scanf. They mirror the print family, but in reverse: they read values from standard input and store them through pointers.
The pointer part is important. To read into a variable, you pass its address (&name), not the variable itself. That's because the function needs to modify the variable, and Go passes everything by value. For now, just remember the &.
Here's fmt.Scan, which reads space-separated values:
Sample run:
fmt.Scan reads tokens separated by whitespace (spaces, tabs, or newlines). It tries to convert each token to match the type of the corresponding pointer. If the user types Priya hello and we expected an int for the second value, the conversion fails and err is non-nil. Always check the error. Ignoring it means your program will happily run with whatever the variable's zero value was.
fmt.Scanln is similar, but it stops reading at the newline. If the user types fewer values than expected before pressing Enter, it returns an error. If they type more, the extras stay in the input buffer for the next read.
Sample run:
fmt.Scanf uses a format string, like Printf in reverse. It matches the input against the format, picking out values where the verbs are:
Sample run:
There's a real limitation here: these functions treat whitespace as a separator, which means you can't easily read a multi-word string with Scan. Asking for a "full name" with fmt.Scan(&name) will only capture the first word. To read a whole line, including spaces, you need bufio.Scanner.
Cost: The fmt scan functions are convenient but slow compared to bufio.Scanner because they parse character by character. For interactive prompts where the user types one or two values, that doesn't matter. For reading thousands of lines from a file, switch to bufio.
So far, every print has gone to standard output (your terminal). The fmt package has a parallel set of functions that write to any destination instead, prefixed with F for "file" (though they work with more than just files). The two you'll see most are fmt.Fprintln and fmt.Fprintf.
The most common use is writing errors to standard error instead of standard output. This matters in real programs: standard output is for the program's actual results, and standard error is for diagnostic messages. When you pipe output to another program or a log file, you usually want errors to stay visible.
Output (on stdout):
Output (on stderr):
os.Stdout and os.Stderr are both io.Writer values. Any type that satisfies io.Writer can be passed to the Fprint family, including files, network connections, and in-memory buffers. The takeaway for now: when you see fmt.Fprintf(w, ...) in someone else's code, w is just a destination, and fmt works with any of them.
The following program exercises most of what's been covered: reading user input, formatting numbers and strings, aligning columns, and printing a polished receipt.
Sample run:
Notice the float64(quantity) conversion. Go won't multiply an int by a float64 directly. Also notice every Scan checks its error. Skip those checks and a typo in the input silently leaves a variable at its zero value, which produces wrong results without any warning.