Go string interpolation: fmt.Sprintf, verbs, and alternatives

Go has no f-strings or ${} interpolation; use fmt.Sprintf and format verbs, Printf vs Sprintf vs Sprint, number padding and bases, struct verbs, Builder and text/template alternatives, and common fmt mistakes.

Published

Updated

Read time 6 min read

Reviewed byDeepak Prasad

Go string interpolation: fmt.Sprintf, verbs, and alternatives

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 go toolchain 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:

go
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

Output:

text
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

go
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

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.

go
package main

import (
	"fmt"
)

func main() {
	service := "nginx"
	status := "running"

	msg := fmt.Sprintf("Service %s is %s", service, status)

	fmt.Println(msg)
}
Output

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%%")
go
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))
}
Output

Format Numbers in Go

Format integers with width, padding, binary, and hex

go
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

Output:

text
Decimal: 42
Binary: 101010
Hex: 2a
Padded: 0042

Format floats with decimal precision

go
package main

import (
	"fmt"
)

func main() {
	price := 19.9876

	fmt.Println(fmt.Sprintf("Price: %.2f", price))
}
Output

Output:

text
Price: 19.99

Format Strings for Real Use Cases

Build log messages

go
level, reqID, msg := "ERROR", "a1b2", "connection reset"
line := fmt.Sprintf("[%s] req=%s %s", level, reqID, msg)

Build filenames and paths

go
id := 42
name := fmt.Sprintf("report-%04d.json", id)

Build API messages and error strings

go
code := 503
body := fmt.Sprintf(`{"error":"upstream","code":%d}`, code)
err := fmt.Errorf("request failed: %s", body)

Format price or percentage values

go
price := 12.3456
usage := 87
s := fmt.Sprintf("price=%.2f usage=%d%%", price, usage)

Format a struct for debugging

go
type Cfg struct{ Host string; Port int }
c := Cfg{"localhost", 8080}
debug := fmt.Sprintf("%+v", c)

Build an error message

go
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

go
userID, lang := 1001, "en"
key := fmt.Sprintf("user:%d:lang:%s", userID, lang)

Format CLI output with padding

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

go
name := "Alice"
wrong := fmt.Sprintf("Name: %d", name)
right := fmt.Sprintf("Name: %s", name)
// wrong contains: Name: %!d(string=Alice)

Missing or extra arguments

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

go
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)

References


Frequently Asked Questions

1. Does Go have string interpolation like JavaScript template literals?

No built-in ${} templates in the language. Use fmt.Sprintf with verbs, or other packages for larger templates.

2. What is the difference between fmt.Sprintf and fmt.Printf?

Sprintf returns a formatted string; Printf writes formatted bytes to os.Stdout and returns the byte count and an optional write error.

3. When should I use fmt.Errorf with %w?

Use %w with an error operand when you want wrapping so callers can use errors.Is and errors.As; otherwise %v is enough for a message-only error.

4. What happens if verbs and arguments do not line up?

fmt still returns a string; extra arguments appear as %!(EXTRA type=value), missing values as %!(MISSING), and wrong types often as %!verb(type=value).

5. Why does fmt.Sprintln not format my %s?

Sprintln does not treat the first argument as a printf format string; use Sprintf (or Printf) when you need % verbs.
Tuan Nguyen

Data Scientist

Proficient in Golang, Python, Java, MongoDB, Selenium, Spring Boot, Kubernetes, Scrapy, API development, Docker, Data Scraping, PrimeFaces, Linux, Data Structures, and Data Mining. With expertise …