Input and Output Beginner¶
Introduction¶
Go's fmt package is the workhorse for formatted input and output. Whether you're printing debug info, formatting log messages, or building strings, you'll use fmt constantly. The format verbs (%v, %s, %d, etc.) are essential knowledge — they appear in debugging, logging, error messages, and interview questions.
Syntax & Usage¶
Basic Output with fmt¶
fmt.Print("no newline")
fmt.Println("with newline") // adds \n
fmt.Printf("formatted: %d\n", 42) // C-style formatting
| Function | Purpose | Output Destination |
|---|---|---|
fmt.Print |
Print without newline | os.Stdout |
fmt.Println |
Print with newline, spaces between args | os.Stdout |
fmt.Printf |
Formatted print | os.Stdout |
fmt.Fprint |
Print to any io.Writer |
Specified writer |
fmt.Fprintf |
Formatted print to any io.Writer |
Specified writer |
fmt.Sprint |
Return string (no print) | Returns string |
fmt.Sprintf |
Formatted string (no print) | Returns string |
fmt.Errorf |
Formatted error | Returns error |
Format Verbs — The Complete Guide¶
General Verbs¶
type User struct {
Name string
Email string
Age int
}
u := User{"Alice", "alice@example.com", 30}
fmt.Printf("%v\n", u) // {Alice alice@example.com 30}
fmt.Printf("%+v\n", u) // {Name:Alice Email:alice@example.com Age:30}
fmt.Printf("%#v\n", u) // main.User{Name:"Alice", Email:"alice@example.com", Age:30}
fmt.Printf("%T\n", u) // main.User
| Verb | Output | Use Case |
|---|---|---|
%v |
Default format | General-purpose printing |
%+v |
Struct with field names | Debugging structs |
%#v |
Go syntax representation | Debugging — shows types and quotes |
%T |
Type of the value | Debugging type mismatches |
%v vs %+v vs %#v — know the difference
This is a common debugging distinction:
%v— quick look:{Alice alice@example.com 30}%+v— which field is which:{Name:Alice Email:alice@example.com Age:30}%#v— copy-pasteable Go code:main.User{Name:"Alice", Email:"alice@example.com", Age:30}
Type-Specific Verbs¶
// Integers
fmt.Printf("%d\n", 42) // 42 decimal
fmt.Printf("%b\n", 42) // 101010 binary
fmt.Printf("%o\n", 42) // 52 octal
fmt.Printf("%x\n", 255) // ff hex (lowercase)
fmt.Printf("%X\n", 255) // FF hex (uppercase)
// Strings
fmt.Printf("%s\n", "hello") // hello string
fmt.Printf("%q\n", "hello") // "hello" quoted string
// Floats
fmt.Printf("%f\n", 3.14) // 3.140000 default precision
fmt.Printf("%.2f\n", 3.14) // 3.14 2 decimal places
fmt.Printf("%e\n", 3.14) // 3.140000e+00 scientific
// Booleans
fmt.Printf("%t\n", true) // true
// Pointers
fmt.Printf("%p\n", &x) // 0xc0000b6010
// Error wrapping (used with fmt.Errorf)
fmt.Errorf("open failed: %w", err) // wraps err for errors.Is/As
Quick Verb Reference Table¶
| Verb | Applies To | Output |
|---|---|---|
%v |
Any | Default format |
%+v |
Structs | With field names |
%#v |
Any | Go-syntax representation |
%T |
Any | Type name |
%d |
Integers | Decimal |
%b |
Integers | Binary |
%o |
Integers | Octal |
%x, %X |
Integers | Hexadecimal |
%s |
Strings | Plain string |
%q |
Strings | Quoted string |
%f |
Floats | Decimal point |
%e, %E |
Floats | Scientific notation |
%t |
Booleans | true or false |
%p |
Pointers | Address in hex |
%w |
Errors | Wrap error (fmt.Errorf only) |
%% |
— | Literal % |
Width and Precision¶
fmt.Printf("%10d\n", 42) // 42 (right-aligned, width 10)
fmt.Printf("%-10d|\n", 42) // 42 | (left-aligned)
fmt.Printf("%010d\n", 42) // 0000000042 (zero-padded)
fmt.Printf("%.5s\n", "hello world") // hello (truncate string)
fmt.Printf("%8.2f\n", 3.14) // 3.14 (width 8, 2 decimal places)
Standard Streams¶
import "os"
os.Stdin // standard input (*os.File, implements io.Reader)
os.Stdout // standard output (*os.File, implements io.Writer)
os.Stderr // standard error (*os.File, implements io.Writer)
// Writing to stderr
fmt.Fprintln(os.Stderr, "error: something went wrong")
// Fprintf for formatted output to any writer
fmt.Fprintf(os.Stdout, "User: %s, Age: %d\n", name, age)
Reading Input with fmt.Scan¶
var name string
var age int
// Reads space-delimited tokens from stdin
fmt.Print("Enter name and age: ")
fmt.Scan(&name, &age)
// Formatted scanning
fmt.Print("Enter date (DD/MM/YYYY): ")
var d, m, y int
fmt.Scanf("%d/%d/%d", &d, &m, &y)
// Scan from a string
var x, y int
fmt.Sscanf("point 10 20", "point %d %d", &x, &y)
fmt.Scan limitations
fmt.Scan reads space-separated tokens and stops at whitespace. It cannot read a full line with spaces. Use bufio.Scanner for line-based input.
Reading Input with bufio.Scanner (Preferred)¶
scanner := bufio.NewScanner(os.Stdin)
fmt.Print("Enter your full name: ")
if scanner.Scan() {
name := scanner.Text() // reads the entire line
fmt.Printf("Hello, %s!\n", name)
}
// Reading multiple lines
fmt.Println("Enter lines (Ctrl+D to stop):")
for scanner.Scan() {
line := scanner.Text()
if line == "" {
break
}
fmt.Println("Got:", line)
}
if err := scanner.Err(); err != nil {
fmt.Fprintln(os.Stderr, "reading input:", err)
}
Reading with bufio.Reader¶
reader := bufio.NewReader(os.Stdin)
fmt.Print("Enter text: ")
// ReadString reads until the delimiter (inclusive)
input, err := reader.ReadString('\n')
if err != nil {
log.Fatal(err)
}
input = strings.TrimSpace(input) // remove trailing \n
Sprintf for String Building¶
// Building strings without printing
msg := fmt.Sprintf("User %s (ID: %d) logged in from %s", name, id, ip)
log.Println(msg)
// Building error messages
return fmt.Errorf("query failed for table %s: %v", table, err)
// Building URLs
url := fmt.Sprintf("https://api.example.com/users/%d/posts?page=%d", userID, page)
Basic File Writing¶
// Quick write (creates or truncates)
err := os.WriteFile("output.txt", []byte("hello\n"), 0644)
if err != nil {
log.Fatal(err)
}
// Using fmt.Fprintf with a file
f, err := os.Create("report.txt")
if err != nil {
log.Fatal(err)
}
defer f.Close()
fmt.Fprintf(f, "Report generated at %s\n", time.Now().Format(time.RFC3339))
fmt.Fprintf(f, "Total users: %d\n", count)
The Stringer Interface¶
Implement String() to control how your type prints with %v and %s.
type Status int
const (
Active Status = iota
Inactive
Banned
)
func (s Status) String() string {
switch s {
case Active:
return "active"
case Inactive:
return "inactive"
case Banned:
return "banned"
default:
return fmt.Sprintf("unknown(%d)", s)
}
}
fmt.Println(Active) // "active" -- not "0"
Quick Reference¶
| Task | Function | Example |
|---|---|---|
| Print to stdout | fmt.Println |
fmt.Println("hello") |
| Formatted print | fmt.Printf |
fmt.Printf("%d", 42) |
| Build string | fmt.Sprintf |
s := fmt.Sprintf("%d", 42) |
| Print to writer | fmt.Fprintf |
fmt.Fprintf(w, "%s", msg) |
| Create error | fmt.Errorf |
fmt.Errorf("fail: %v", err) |
| Read tokens | fmt.Scan |
fmt.Scan(&x, &y) |
| Read line | bufio.Scanner |
scanner.Scan(); scanner.Text() |
| Read until delim | bufio.Reader |
reader.ReadString('\n') |
| Write file | os.WriteFile |
os.WriteFile(path, data, 0644) |
| Custom printing | String() string |
Implement fmt.Stringer |
Best Practices¶
- Use
%vfor general logging,%+vwhen debugging structs,%#vfor detailed inspection. - Use
fmt.Sprintfto build strings — not string concatenation in loops. - Use
fmt.Errorffor error context — it's the idiomatic way to add context to errors. - Prefer
bufio.Scanneroverfmt.Scanfor reading user input — it handles full lines. - Always check
scanner.Err()after the scan loop finishes. - Write to
os.Stderrfor error messages — keep stdout clean for actual output. - Implement
fmt.Stringerfor types that need human-readable output.
Common Pitfalls¶
Scanner default buffer size
bufio.Scanner has a default max token size of 64KB. For large lines:
Printf with wrong verb type
fmt.Printf("%d\n", "hello") // %!d(string=hello) -- wrong verb
fmt.Printf("%s\n", 42) // %!s(int=42) -- wrong verb
go vet to catch these at compile time.
Forgetting newline in Printf
Scan doesn't read full lines
Performance Considerations¶
| Operation | Performance Note |
|---|---|
fmt.Sprintf in hot loops |
Allocates — use strconv for simple int/float conversion |
fmt.Fprintf to bufio.Writer |
Buffer writes for better I/O throughput |
+ string concatenation in loops |
O(n²) — use strings.Builder instead |
fmt.Println for logging |
Fine for development; use structured logging (slog) in production |
// Hot path: avoid fmt.Sprintf for simple conversions
s := strconv.Itoa(42) // faster than fmt.Sprintf("%d", 42)
s := strconv.FormatFloat(3.14, 'f', 2, 64) // faster than fmt.Sprintf("%.2f", 3.14)
// String building in loops
var b strings.Builder
for _, name := range names {
b.WriteString(name)
b.WriteByte('\n')
}
result := b.String()
Interview Tips¶
Interview Tip
"What's the difference between %v, %+v, and %#v?" This is a common debugging question.
%v— default format:{Alice 30}%+v— adds field names:{Name:Alice Age:30}%#v— Go syntax with types:main.User{Name:"Alice", Age:30}
In practice, %+v is your go-to for debugging structs.
Interview Tip
"What is the fmt.Stringer interface?" Any type implementing String() string controls how it appears when printed with %v or %s. It's Go's equivalent of Java's toString() or Python's __str__().
Interview Tip
"How do you read user input in Go?" Use bufio.NewScanner(os.Stdin) with scanner.Scan() and scanner.Text() for line-based input. fmt.Scan only reads space-delimited tokens. Always check scanner.Err() after the loop.
Key Takeaways¶
fmt.Printffor formatted output,fmt.Sprintffor building strings,fmt.Errorffor errors.- Know your verbs:
%v(default),%+v(fields),%#v(Go syntax),%T(type),%d(int),%s(string),%f(float). Printlnadds newlines,Printfdoes not — common source of formatting bugs.- Use
bufio.Scannerfor line-based input, notfmt.Scan. - Implement
fmt.Stringer(String() string) for custom type printing. - In hot paths, use
strconvfunctions instead offmt.Sprintffor simple conversions. - Write errors and diagnostics to
os.Stderr, program output toos.Stdout.