Question
Both fmt.Println() and println() appear to print the same text in Go:
package main
import "fmt"
func main() {
fmt.Println("Hello world!")
}
package main
func main() {
println("Hello world!")
}
How does fmt.Println() differ from the built-in println() function, and when should each one be used?
Short Answer
You will learn why Go provides both fmt.Println and the built-in println, how their formatting and output behavior differ, and why production programs normally use the fmt package or a logging package instead of println.
Concept
fmt.Println and println both can display values while you are running a Go program, but they serve different purposes.
fmt.Printlnis an exported function in the standard-libraryfmtpackage.printlnis a predeclared built-in function supplied by the Go language.
fmt.Println is designed for normal program output. It formats its arguments, writes to standard output (os.Stdout), separates arguments with spaces, and ends with a newline. Its behavior is documented and appropriate for command-line programs, examples, and simple user-facing output.
println is intended mainly as a low-level debugging aid. Its output format is implementation-specific, and the Go specification does not promise that it writes to standard output or that its output is suitable for users. In typical Go implementations, it writes diagnostic output to standard error (os.Stderr), but code should not rely on this as a portable interface.
This matters because output destination, predictable formatting, testability, and error handling are important in real programs. A command-line tool may send normal results to standard output so they can be redirected to a file or piped into another command. Debugging messages should usually go somewhere else.
Mental Model
Think of fmt.Println as a printer installed for everyday office work: it has a documented interface, predictable output, and a known destination.
Think of println as a maintenance display built into the machine: it is convenient when diagnosing a problem, but it is not the display you design your product around.
Both may show Hello world! on your terminal, but seeing the same result does not mean they have the same contract or intended use.
Syntax and Examples
Use fmt.Println after importing fmt:
package main
import "fmt"
func main() {
fmt.Println("Hello", "world!")
fmt.Println("Items:", 3)
}
Output:
Hello world!
Items: 3
fmt.Println:
- accepts multiple values;
- inserts spaces between separate arguments;
- appends one newline;
- writes normal output to standard output;
- returns the number of bytes written and a possible write error.
n, err := fmt.Println("Saved", 3, "records")
if err != nil {
// Handle an output error when it matters.
return
}
_ = n
The built-in println needs no import:
package main
func main() {
println("Hello", "world!")
println("Items:", 3)
}
It is handy for quick temporary checks, but its formatting and output destination are not a stable API for your application. Do not use it for normal CLI output, files, protocols, or automated tests that depend on exact output.
Step by Step Execution
Consider this program:
package main
import "fmt"
func main() {
name := "Mina"
count := 2
fmt.Println("User:", name, "Count:", count)
println("debug count:", count)
}
Step by step:
namestores the string"Mina".countstores the integer2.fmt.Printlnreceives four arguments:"User:",name,"Count:", andcount.- It converts the values to readable text, places spaces between arguments, appends a newline, and writes normal output to standard output.
printlnreceives a string and an integer.- It emits diagnostic-style output with implementation-dependent details. It also appends a newline.
On a common Go toolchain, both lines may appear in the terminal. However, if you redirect standard output, the fmt.Println line is the one intended to be captured as program output.
Real World Use Cases
- Command-line tools: Use
fmt.Printlnto print a generated report, a filename, or a command result. - Small learning programs: Use
fmt.Printlnto show values while practicing variables, loops, and functions. - Examples and documentation: Use
fmt.Printlnwhen example output should be clear and predictable. - Temporary local debugging: Use
printlnonly for a quick, disposable check while investigating code. - User-facing errors in simple CLI programs: Use
fmt.Fprintln(os.Stderr, ...)rather than either default option when the message is specifically an error. - Production diagnostics: Prefer the
logpackage or a structured logging library, because logs often need timestamps, severity levels, and configurable destinations.
Real Codebase Usage
In real Go code, developers usually choose an output tool based on the message's purpose.
Normal output
Use fmt.Println for simple user-visible output:
fmt.Println("Import completed")
For code that should be easy to test or redirect, write to an explicit writer:
func printStatus(out io.Writer, processed int) error {
_, err := fmt.Fprintln(out, "Processed:", processed)
return err
}
The caller can pass os.Stdout in the application and a bytes.Buffer in a test.
Errors and diagnostics
Send errors to standard error explicitly:
if err != nil {
fmt.Fprintln(os.Stderr, "Could not load configuration:", err)
return
}
Logging
Use log when messages are operational diagnostics rather than application output:
log.Printf("processed %d records", processed)
Temporary debugging
A developer may briefly add println("checkpoint") during investigation, but should generally remove it before committing. It has no configurable destination, no documented formatting contract for application output, and no useful error result.
Common Mistakes
Treating println as a replacement for fmt.Println
This may look harmless:
println("Export complete")
Avoid using it for a tool's official output. Its destination and formatting are not guaranteed by the language specification. Use:
fmt.Println("Export complete")
Assuming both functions write to the same stream
They can appear together in a terminal, but they are not equivalent. fmt.Println writes to standard output. Do not depend on println being captured by stdout redirection.
Ignoring output errors when they matter
For a short example, ignoring fmt.Println's returned error is common. For important output, such as writing a report through a pipe or network-backed writer, check the error:
if _, err := fmt.Fprintln(out, report); err != nil {
return err
}
Using fmt.Println when formatting is required
Println inserts spaces between arguments. It does not interpret %s or %d formatting verbs:
// Broken expectation: this prints the percent signs literally.
fmt.Println("User %s has %d items", "Mina", 2)
Use fmt.Printf instead:
fmt.Printf("User %s has %d items\n", "Mina", 2)
Leaving debugging output in committed code
Temporary println calls can leak internal values, clutter output, and confuse users. Remove them or replace them with intentional logging where needed.
Comparisons
| Tool | Purpose | Default destination | Formatting contract | Typical use |
|---|---|---|---|---|
println | Low-level debugging aid | Not specified by the language | Implementation-specific | Temporary debugging only |
fmt.Println | Normal text output | Standard output | Documented fmt behavior | CLI output and examples |
fmt.Fprintln | Text output to a chosen writer | The writer you provide | Documented fmt behavior | Testable and redirectable output |
fmt.Printf | Formatted text output | Standard output | Uses format verbs such as %s and %d | Precise output layout |
log.Println | Operational diagnostics | Logger's configured writer | Includes logger behavior such as prefixes | Logs and troubleshooting |
fmt.Println versus fmt.Printf:
fmt.Println("Total:", 12) // Total: 12
fmt.Printf("Total: %d\n", 12) // Total: 12
Use Println when separating values with spaces is enough. Use Printf when you need formatting verbs, width, precision, or a specific layout.
Cheat Sheet
println(...)is a predeclared built-in; no import is needed.fmt.Println(...)belongs to thefmtpackage; addimport "fmt".- Use
fmt.Printlnfor regular terminal or command-line output. fmt.Printlnwrites toos.Stdoutand adds a newline.printlnis intended for debugging; do not rely on its destination or exact format.fmt.Println("A", "B")printsA Bfollowed by a newline.fmt.Printf("%s: %d\n", name, count)is for format verbs.fmt.Fprintln(writer, ...)is best when you need a chosen destination or easy tests.fmt.Fprintln(os.Stderr, ...)is a common pattern for user-visible error messages.- Prefer
logor structured logging for durable application diagnostics.
FAQ
Is println part of the Go language?
Yes. println is a predeclared built-in function, so it can be called without importing a package.
Is fmt.Println better than println?
For normal application output, yes. fmt.Println has documented behavior and writes to standard output. println is mainly a debugging aid.
Why does println appear to work like fmt.Println?
For simple strings and numbers, both often display similar text in a terminal. Their intended purpose, output guarantees, and formatting behavior still differ.
Does println always write to standard error?
Do not rely on that. The Go specification does not define its output destination. A particular implementation may write diagnostic output to standard error.
Should I use println in production Go code?
Usually no. Use fmt, log, or a project-approved logging system depending on whether the message is normal output, an error, or a diagnostic log.
Which function should I use for errors in a CLI program?
A common choice is fmt.Fprintln(os.Stderr, "message"), because it explicitly sends the message to standard error.
Can I test output from fmt.Println?
Direct calls use standard output, but you can make code testable by accepting an io.Writer and calling fmt.Fprintln(writer, ...). Tests can pass a buffer as that writer.
Mini Project
Description
Build a small command-line report function that writes a task summary to an output destination supplied by the caller. This demonstrates why explicit writers and fmt.Fprintln are more reliable than using println for application output.
Goal
Create a function that prints a task summary to standard output and reports an error if writing fails.
Requirements
Use a Task struct with a name and completion status.
Keep learning
Related questions
Are Go Maps Passed by Value or Reference?
Learn Go map parameter and return semantics: maps are passed by value, while copies share underlying data. Know when map pointers are useful.
Authentication for Go REST APIs: Sessions, Tokens, and Middleware
Learn practical authentication patterns for Go REST APIs, including password hashing, server sessions, bearer tokens, middleware, and security rules.
Automatic Build Versioning in Go: Embed Incrementing Build Numbers
Learn how to add automatic build versioning in Go using linker flags, build metadata, CI counters, and Git-based version values.