Skip to content

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

  1. Use %v for general logging, %+v when debugging structs, %#v for detailed inspection.
  2. Use fmt.Sprintf to build strings — not string concatenation in loops.
  3. Use fmt.Errorf for error context — it's the idiomatic way to add context to errors.
  4. Prefer bufio.Scanner over fmt.Scan for reading user input — it handles full lines.
  5. Always check scanner.Err() after the scan loop finishes.
  6. Write to os.Stderr for error messages — keep stdout clean for actual output.
  7. Implement fmt.Stringer for 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:

scanner := bufio.NewScanner(file)
scanner.Buffer(make([]byte, 1024*1024), 1024*1024) // 1MB buffer

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 doesn't crash — it prints a diagnostic. Use go vet to catch these at compile time.

Forgetting newline in Printf

fmt.Printf("value: %d", 42)  // no newline -- next output on same line
fmt.Printf("value: %d\n", 42) // correct
// Println adds newline automatically, Printf does not

Scan doesn't read full lines

var input string
fmt.Scan(&input)
// Input: "John Doe"
// input = "John" -- stops at space!
// Use bufio.Scanner for full-line reading

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.Printf for formatted output, fmt.Sprintf for building strings, fmt.Errorf for errors.
  • Know your verbs: %v (default), %+v (fields), %#v (Go syntax), %T (type), %d (int), %s (string), %f (float).
  • Println adds newlines, Printf does not — common source of formatting bugs.
  • Use bufio.Scanner for line-based input, not fmt.Scan.
  • Implement fmt.Stringer (String() string) for custom type printing.
  • In hot paths, use strconv functions instead of fmt.Sprintf for simple conversions.
  • Write errors and diagnostics to os.Stderr, program output to os.Stdout.