I stick to the official fmt documentation for verb rules. When I prepared this tutorial, I ran every snippet with go run on Go 1.22 (64-bit Linux), including the intentional mistakes, so you can trust the %!… output you see here against what you get on your machine.
Tested on: Go 1.22 on 64-bit Linux; snippets were run with the standard
gotoolchain while this guide was updated.
Does Go Support String Interpolation?
Go does not support native string interpolation like Python f-strings, JavaScript template literals, or Ruby #{} syntax inside a string literal.
In Go, the usual way to build a formatted string is fmt.Sprintf:
package main
import (
"fmt"
)
func main() {
name := "Deepak"
age := 30
message := fmt.Sprintf("My name is %s and I am %d years old.", name, age)
fmt.Println(message)
}Output:
My name is Deepak and I am 30 years old.Here, %s is for a string and %d is for an integer.
That pattern is what people mean when they search for golang string interpolation, go string interpolation, or string interpolation golang: explicit format strings and arguments, not embedded expressions.
There have been language design discussions and proposals around richer string building; standard Go today still centers on fmt.Sprintf and related helpers (and alternatives below).
Go string interpolation vs fmt.Sprintf
“Interpolation” in other languages often means syntax inside the string. In Go, the format string is always a separate argument, which keeps ordering and types visible at compile time for the format string literal (values are still checked at run time for fmt).
| Goal | Use | Example |
|---|---|---|
| Print formatted output | fmt.Printf |
fmt.Printf("Name: %s", name) |
| Return formatted string | fmt.Sprintf |
msg := fmt.Sprintf("Name: %s", name) |
| Join simple values (no verbs) | fmt.Sprint |
fmt.Sprint("ID:", id) |
| Print with newline | fmt.Println |
fmt.Println(msg) |
| Simple literal join | + operator |
"Hello " + name |
| Many repeated joins | strings.Builder |
Build in a loop with WriteString |
| Large templates | text/template |
HTML, email bodies, config text |
fmt.Sprintln adds spaces between operands and a trailing newline; it does not apply % verbs like Sprintf—see Common fmt.Sprintf Mistakes.
Format Strings in Go Using fmt.Sprintf
Basic fmt.Sprintf example
package main
import (
"fmt"
)
func main() {
name := "Deepak"
age := 30
message := fmt.Sprintf("My name is %s and I am %d years old.", name, age)
fmt.Println(message)
}Format a string without printing
Use fmt.Sprintf when you need a string value (log fields, HTTP bodies, keys, errors) instead of writing directly to stdout. This is the common answer to “how do I format without printing?”—fmt.Sprintf returns the formatted string; fmt.Printf writes to standard output.
package main
import (
"fmt"
)
func main() {
service := "nginx"
status := "running"
msg := fmt.Sprintf("Service %s is %s", service, status)
fmt.Println(msg)
}Common Go Formatting Verbs
The fmt package documents Printf, Sprintf, Fprintf, and Appendf as the functions that consume format verbs. Go by Example: String Formatting walks through common combinations.
Format strings, integers, floats, and booleans
| Verb | Meaning | Example |
|---|---|---|
%s |
String | fmt.Sprintf("Hello %s", name) |
%d |
Decimal integer | fmt.Sprintf("Age: %d", age) |
%f |
Floating-point | fmt.Sprintf("Price: %f", price) |
%.2f |
Float with 2 decimals | fmt.Sprintf("%.2f", price) |
%t |
Boolean | fmt.Sprintf("Active: %t", active) |
%v |
Default value format | fmt.Sprintf("Value: %v", value) |
%+v |
Struct with field names | fmt.Sprintf("%+v", user) |
%#v |
Go-syntax representation | fmt.Sprintf("%#v", user) |
%T |
Type of value | fmt.Sprintf("%T", value) |
%q |
Quoted string | fmt.Sprintf("%q", text) |
%% |
Literal percent | fmt.Sprintf("CPU: 80%%") |
Print structs with %v, %+v, and %#v
package main
import "fmt"
type User struct {
Name string
ID int
}
func main() {
u := User{Name: "Ada", ID: 7}
fmt.Println(fmt.Sprintf("%v", u))
fmt.Println(fmt.Sprintf("%+v", u))
fmt.Println(fmt.Sprintf("%#v", u))
}Format Numbers in Go
Format integers with width, padding, binary, and hex
package main
import (
"fmt"
)
func main() {
n := 42
fmt.Println(fmt.Sprintf("Decimal: %d", n))
fmt.Println(fmt.Sprintf("Binary: %b", n))
fmt.Println(fmt.Sprintf("Hex: %x", n))
fmt.Println(fmt.Sprintf("Padded: %04d", n))
}Output:
Decimal: 42
Binary: 101010
Hex: 2a
Padded: 0042Format floats with decimal precision
package main
import (
"fmt"
)
func main() {
price := 19.9876
fmt.Println(fmt.Sprintf("Price: %.2f", price))
}Output:
Price: 19.99Format Strings for Real Use Cases
Build log messages
level, reqID, msg := "ERROR", "a1b2", "connection reset"
line := fmt.Sprintf("[%s] req=%s %s", level, reqID, msg)Build filenames and paths
id := 42
name := fmt.Sprintf("report-%04d.json", id)Build API messages and error strings
code := 503
body := fmt.Sprintf(`{"error":"upstream","code":%d}`, code)
err := fmt.Errorf("request failed: %s", body)Format price or percentage values
price := 12.3456
usage := 87
s := fmt.Sprintf("price=%.2f usage=%d%%", price, usage)Format a struct for debugging
type Cfg struct{ Host string; Port int }
c := Cfg{"localhost", 8080}
debug := fmt.Sprintf("%+v", c)Build an error message
op := "dial"
err := fmt.Errorf("%s: timeout", op)For wrapping an underlying error, use %w with fmt.Errorf so callers can use errors.Is / errors.As.
Generate a cache key
userID, lang := 1001, "en"
key := fmt.Sprintf("user:%d:lang:%s", userID, lang)Format CLI output with padding
rows := []struct{ Name string; Score int }{{"Ann", 9}, {"Bob", 10}}
for _, r := range rows {
fmt.Printf("%-10s %3d\n", r.Name, r.Score)
}Alternatives to fmt.Sprintf
Use string concatenation for simple strings
For two or three literals and variables, + or a single Sprint can be clearer than a long format string.
Use strings.Builder for repeated string building
When you append in a loop, a strings.Builder avoids repeated full-string copies; you can still use fmt.Fprintf(&b, ...) to write formatted fragments into the builder.
Use text/template for larger templates
For HTML, emails, or large config snippets, text/template (or html/template for web HTML) scales better than huge Sprintf chains.
For strict string-to-number conversion without a format string, see strconv in Go.
Common fmt.Sprintf Mistakes
Wrong formatting verb
Using %d with a string does not compile-fail; fmt records the problem in the output string:
name := "Alice"
wrong := fmt.Sprintf("Name: %d", name)
right := fmt.Sprintf("Name: %s", name)
// wrong contains: Name: %!d(string=Alice)Missing or extra arguments
missing := fmt.Sprintf("Name: %s, Age: %d", "Alice")
extra := fmt.Sprintf("Name: %s", "Alice", 30)
// missing includes: Age: %!d(MISSING)
// extra includes trailing: %!(EXTRA int=30)Using fmt.Sprintln instead of fmt.Sprintf
fmt.Sprintln does not treat the first argument as a printf-style format string, so %s stays literal:
name := "Alice"
wrong := fmt.Sprintln("Hello %s", name)
right := fmt.Sprintf("Hello %s", name)
// wrong is like: "Hello %s Alice\n" (with spaces between args)
// right is: "Hello Alice"Go String Formatting Cheat Sheet
fmt.Sprintf vs fmt.Printf vs fmt.Sprint
| Task | Recommended option |
|---|---|
| Insert string into text | fmt.Sprintf("Hello %s", name) |
| Insert integer | fmt.Sprintf("ID: %d", id) |
| Insert any value | fmt.Sprintf("Value: %v", value) |
| Print struct fields | fmt.Sprintf("%+v", user) |
| Show Go syntax | fmt.Sprintf("%#v", value) |
| Format float to 2 decimals | fmt.Sprintf("%.2f", price) |
| Add leading zeroes | fmt.Sprintf("%04d", n) |
| Format binary | fmt.Sprintf("%b", n) |
| Format hexadecimal | fmt.Sprintf("%x", n) |
| Add literal percent | fmt.Sprintf("CPU: %d%%", usage) |
| Format without printing | fmt.Sprintf(...) |
| Print formatted output | fmt.Printf(...) |
| Simple concatenation | "Hello " + name |
| Repeated string building | strings.Builder (optionally fmt.Fprintf into it) |

