Strconv #

Every Go application that reads input from the outside — web forms, CLI arguments, config files, URL query parameters, or queue messages — needs to convert strings into processable data types: integers for IDs and numbers, floats for decimal values, bools for active/inactive flags. The strconv package is the tool for all these conversions. It’s faster and more explicit than fmt.Sprintf because it’s designed for a single purpose: converting between strings and Go’s primitive types. Understanding strconv well means understanding how to handle untrusted input correctly — because every conversion that can fail returns an error that must be checked, not ignored.

An Overview of the strconv Package #

flowchart LR
    subgraph Parse["String → Other Types"]
        P1["strconv.Atoi\nstring → int"]
        P2["strconv.ParseInt\nstring → int64 (any base)"]
        P3["strconv.ParseFloat\nstring → float64"]
        P4["strconv.ParseBool\nstring → bool"]
        P5["strconv.ParseUint\nstring → uint64"]
    end

    subgraph Format["Other Types → String"]
        F1["strconv.Itoa\nint → string"]
        F2["strconv.FormatInt\nint64 → string (any base)"]
        F3["strconv.FormatFloat\nfloat64 → string"]
        F4["strconv.FormatBool\nbool → string"]
        F5["strconv.FormatUint\nuint64 → string"]
    end

    subgraph Quote["String Escaping"]
        Q1["strconv.Quote\nadd quotes & escape"]
        Q2["strconv.Unquote\nremove quotes & unescape"]
        Q3["strconv.AppendQuote\nappend to []byte"]
    end

    subgraph Errors["Possible Errors"]
        E1["*strconv.NumError\n  .Err: ErrSyntax\n  .Err: ErrRange\n  .Num: the input string"]
    end

    Parse --> Errors
    Format --> Str["string"]
    Parse --> Val["Go value"]

    style Parse fill:#e8f5e9
    style Format fill:#e3f2fd
    style Quote fill:#fff3e0
    style Errors fill:#fce4ec

Integer Conversions #

Atoi and Itoa — The Most Common Shortcuts #

Atoi (ASCII to Integer) and Itoa (Integer to ASCII) are the most used functions from the strconv package — direct conversions between string and int.

package main

import (
    "fmt"
    "strconv"
)

func main() {
    // Itoa — int to string, never fails
    s := strconv.Itoa(42)
    fmt.Println(s)        // "42"
    fmt.Printf("%T\n", s) // string

    s2 := strconv.Itoa(-100)
    fmt.Println(s2) // "-100"

    // Atoi — string to int, can fail
    n, err := strconv.Atoi("42")
    if err != nil {
        fmt.Println("error:", err)
        return
    }
    fmt.Println(n)        // 42
    fmt.Printf("%T\n", n) // int

    // Error when the input isn't a number
    _, err = strconv.Atoi("abc")
    fmt.Println(err) // strconv.Atoi: parsing "abc": invalid syntax

    // Error when the number is too large for an int
    _, err = strconv.Atoi("99999999999999999999999")
    fmt.Println(err) // strconv.Atoi: parsing "99999999999999999999999": value out of range
}

ParseInt — Full Control #

ParseInt gives control over the number base (decimal, hexadecimal, octal, binary) and the result’s bit size:

// ParseInt(s string, base int, bitSize int) (int64, error)
// base: 0 (auto-detect), 2, 8, 10, 16
// bitSize: 0 (int), 8, 16, 32, 64

// Base 10 — ordinary decimal
n, _ := strconv.ParseInt("255", 10, 64)
fmt.Println(n) // 255

// Base 16 — hexadecimal
n, _ = strconv.ParseInt("ff", 16, 64)
fmt.Println(n) // 255

n, _ = strconv.ParseInt("FF", 16, 64)
fmt.Println(n) // 255

// Base 2 — binary
n, _ = strconv.ParseInt("11111111", 2, 64)
fmt.Println(n) // 255

// Base 8 — octal
n, _ = strconv.ParseInt("377", 8, 64)
fmt.Println(n) // 255

// Base 0 — auto-detect from the prefix
n, _ = strconv.ParseInt("0xff", 0, 64)  // 0x prefix → hex
fmt.Println(n) // 255

n, _ = strconv.ParseInt("0377", 0, 64)  // 0 prefix → octal
fmt.Println(n) // 255

n, _ = strconv.ParseInt("0b11111111", 0, 64) // 0b prefix → binary
fmt.Println(n) // 255

n, _ = strconv.ParseInt("255", 0, 64)   // no prefix → decimal
fmt.Println(n) // 255

// bitSize limits the valid range
n32, err := strconv.ParseInt("32768", 10, 16) // max int16 is 32767
fmt.Println(n32, err)
// 32767 strconv.ParseInt: parsing "32768": value out of range
// Note: the returned value is the clamped upper bound, not 0!

FormatInt — Integer to String with a Base #

// FormatInt(i int64, base int) string

n := int64(255)

fmt.Println(strconv.FormatInt(n, 10)) // "255"   — decimal
fmt.Println(strconv.FormatInt(n, 16)) // "ff"    — lowercase hexadecimal
fmt.Println(strconv.FormatInt(n, 2))  // "11111111" — binary
fmt.Println(strconv.FormatInt(n, 8))  // "377"   — octal
fmt.Println(strconv.FormatInt(n, 36)) // "73"    — base 36 (0-9, a-z)

// For a plain int (not int64), convert first
x := 42
fmt.Println(strconv.FormatInt(int64(x), 16)) // "2a"

// Or use Itoa for base 10
fmt.Println(strconv.Itoa(x)) // "42"

ParseUint and FormatUint — Unsigned Integers #

// For values that are never negative (IDs, sizes, ports)
u, err := strconv.ParseUint("65535", 10, 16) // uint16 max
fmt.Println(u, err) // 65535 <nil>

// Port number — uint16
port, err := strconv.ParseUint("8080", 10, 16)
if err != nil {
    fmt.Println("invalid port:", err)
    return
}
fmt.Printf("Port: %d\n", port) // Port: 8080

// Format unsigned
fmt.Println(strconv.FormatUint(uint64(255), 16)) // "ff"
fmt.Println(strconv.FormatUint(uint64(255), 2))  // "11111111"

Float Conversions #

ParseFloat — String to Float #

// ParseFloat(s string, bitSize int) (float64, error)
// bitSize: 32 for float32, 64 for float64

// Parse a float64
f, err := strconv.ParseFloat("3.14159", 64)
if err != nil {
    fmt.Println("error:", err)
    return
}
fmt.Println(f)         // 3.14159
fmt.Printf("%T\n", f)  // float64

// Scientific notation
f, _ = strconv.ParseFloat("1.5e10", 64)
fmt.Println(f) // 1.5e+10

f, _ = strconv.ParseFloat("2.5E-3", 64)
fmt.Println(f) // 0.0025

// Special values
f, _ = strconv.ParseFloat("Inf", 64)
fmt.Println(f) // +Inf

f, _ = strconv.ParseFloat("-Inf", 64)
fmt.Println(f) // -Inf

f, _ = strconv.ParseFloat("NaN", 64)
fmt.Println(f) // NaN

// bitSize 32 — float32 precision but returned as float64
f32, _ := strconv.ParseFloat("3.14159265358979", 32)
fmt.Println(f32)           // 3.1415927410125732 — float32 precision
fmt.Println(float32(f32))  // 3.1415927 — cast to float32

FormatFloat — Float to String #

FormatFloat gives full control over the output format and precision — this is what distinguishes it from fmt.Sprintf("%.2f", f).

// FormatFloat(f float64, fmt byte, prec, bitSize int) string
// fmt: 'f' (decimal), 'e' (scientific), 'g' (shortest), 'b' (binary), 'x' (hex)
// prec: precision (-1 for the minimum precision that exactly represents the value)
// bitSize: 32 or 64

f := 3.14159265358979

// 'f' format — fixed decimal
fmt.Println(strconv.FormatFloat(f, 'f', 2, 64))  // "3.14"
fmt.Println(strconv.FormatFloat(f, 'f', 5, 64))  // "3.14159"
fmt.Println(strconv.FormatFloat(f, 'f', -1, 64)) // "3.14159265358979"

// 'e' format — scientific notation
fmt.Println(strconv.FormatFloat(f, 'e', 2, 64))  // "3.14e+00"
fmt.Println(strconv.FormatFloat(f, 'e', -1, 64)) // "3.14159265358979e+00"

// 'g' format — shortest (scientific or decimal, whichever is shorter)
fmt.Println(strconv.FormatFloat(f, 'g', -1, 64)) // "3.14159265358979"
fmt.Println(strconv.FormatFloat(1e10, 'g', -1, 64)) // "1e+10"

// ANTI-PATTERN: Sprintf for float round-trips
pi := 3.14159265358979323846
s := fmt.Sprintf("%f", pi)   // "3.141593" — loses precision!
f2, _ := strconv.ParseFloat(s, 64)
fmt.Println(f2 == pi) // false — not the same as the original

// CORRECT: FormatFloat with prec -1 for exact round-trips
s2 := strconv.FormatFloat(pi, 'f', -1, 64)
f3, _ := strconv.ParseFloat(s2, 64)
fmt.Println(f3 == pi) // true — the exact same value

Boolean Conversions #

// ParseBool — string to bool
// Accepts: "1", "t", "T", "TRUE", "true", "True" → true
// Accepts: "0", "f", "F", "FALSE", "false", "False" → false

b, err := strconv.ParseBool("true")
fmt.Println(b, err) // true <nil>

b, _ = strconv.ParseBool("1")
fmt.Println(b) // true

b, _ = strconv.ParseBool("T")
fmt.Println(b) // true

b, _ = strconv.ParseBool("false")
fmt.Println(b) // false

b, _ = strconv.ParseBool("0")
fmt.Println(b) // false

_, err = strconv.ParseBool("yes") // invalid!
fmt.Println(err) // strconv.ParseBool: parsing "yes": invalid syntax

// FormatBool — bool to string
fmt.Println(strconv.FormatBool(true))  // "true"
fmt.Println(strconv.FormatBool(false)) // "false"

Pattern: Reading Flags from the Environment #

// Environment variables are often represented as bools
func getEnvBool(key string, defaultVal bool) bool {
    val, exists := os.LookupEnv(key)
    if !exists || val == "" {
        return defaultVal
    }

    b, err := strconv.ParseBool(val)
    if err != nil {
        // Log a warning — invalid value, use the default
        fmt.Fprintf(os.Stderr, "warning: %s=%q is not a valid bool, using %v\n",
            key, val, defaultVal)
        return defaultVal
    }
    return b
}

// Usage
debugMode := getEnvBool("APP_DEBUG", false)
tlsEnabled := getEnvBool("TLS_ENABLED", true)

Understanding NumError #

All Parse* functions return *strconv.NumError on failure. Understanding its structure enables more specific error handling.

flowchart TD
    Err["*strconv.NumError"] --> Func["Func: the function name\n('Atoi', 'ParseInt', etc.)"]
    Err --> Num["Num: the input string\nthat failed to parse"]
    Err --> ErrType["Err: the error type"]

    ErrType --> Syntax["strconv.ErrSyntax\ninput is not a valid number format\ne.g.: 'abc', '12.3' for int"]
    ErrType --> Range["strconv.ErrRange\na valid number but out of range\ne.g.: '999' for uint8 (max 255)"]

    Syntax --> Handle1["Show a message\n'invalid format'"]
    Range --> Handle2["Show a message\n'number too large/small'"]

    style Err fill:#fce4ec
    style Syntax fill:#fff3e0
    style Range fill:#ffebee
import (
    "errors"
    "strconv"
)

func parseUserID(s string) (int64, error) {
    id, err := strconv.ParseInt(s, 10, 64)
    if err != nil {
        // Check the error type for a more informative message
        var numErr *strconv.NumError
        if errors.As(err, &numErr) {
            switch numErr.Err {
            case strconv.ErrSyntax:
                return 0, fmt.Errorf("user ID %q is not a valid number", s)
            case strconv.ErrRange:
                return 0, fmt.Errorf("user ID %q is too large", s)
            }
        }
        return 0, fmt.Errorf("parseUserID: %w", err)
    }
    if id <= 0 {
        return 0, fmt.Errorf("user ID must be positive, got: %d", id)
    }
    return id, nil
}

// Usage
id, err := parseUserID("abc")
// error: user ID "abc" is not a valid number

id, err = parseUserID("99999999999999999999")
// error: user ID "99999999999999999999" is too large

id, err = parseUserID("42")
// id: 42, err: nil

Quote and Unquote — String Escaping #

The Quote and Unquote functions are useful for debugging, logging, and handling strings that may contain special or unprintable characters.

// Quote — add double quotes and escape special characters
s := "Hello\tWorld\n"
fmt.Println(strconv.Quote(s))
// "Hello\tWorld\n"  — shown with literal escape sequences

s2 := `This "quoted" and this\tbackslash`
fmt.Println(strconv.Quote(s2))
// "This \"quoted\" and this\\tbackslash"

// Unicode characters
s3 := "Bahasa Indonesia: é à ü"
fmt.Println(strconv.Quote(s3))
// "Bahasa Indonesia: é à ü"  — printable characters aren't escaped

s4 := string([]byte{0x00, 0x01, 0x1f}) // control characters
fmt.Println(strconv.Quote(s4))
// "\x00\x01\x1f"

// QuoteToASCII — escape all non-ASCII
fmt.Println(strconv.QuoteToASCII("Héllo"))
// "H\u00e9llo"

// Unquote — the inverse of Quote
original, err := strconv.Unquote(`"Hello\tWorld\n"`)
fmt.Println(original, err)
// Hello	World
// <nil>

// IsPrint — can the rune be printed?
fmt.Println(strconv.IsPrint('A'))    // true
fmt.Println(strconv.IsPrint('\t'))   // false — tab isn't printable
fmt.Println(strconv.IsPrint('é'))    // true

// CanBackquote — can the string be represented as a raw string literal?
fmt.Println(strconv.CanBackquote("Hello World"))  // true
fmt.Println(strconv.CanBackquote("Hello\nWorld")) // false — there's a newline
fmt.Println(strconv.CanBackquote("Hello`World"))  // false — there's a backtick

Append Variants — Zero Allocation #

The strconv package provides Append* variants for all Format functions — these enable direct conversion into an existing byte slice without allocating a new string.

// Append variants — useful for building output without extra allocations
buf := make([]byte, 0, 64)

// AppendInt — append the integer representation to the slice
buf = strconv.AppendInt(buf, 255, 16)      // ff
buf = append(buf, ' ')
buf = strconv.AppendInt(buf, 255, 2)       // 11111111
buf = append(buf, ' ')
buf = strconv.AppendInt(buf, -42, 10)      // -42
fmt.Println(string(buf)) // "ff 11111111 -42"

// AppendFloat
buf = buf[:0] // reset without reallocating
buf = strconv.AppendFloat(buf, 3.14159, 'f', 2, 64)
fmt.Println(string(buf)) // "3.14"

// AppendBool
buf = buf[:0]
buf = strconv.AppendBool(buf, true)
buf = append(buf, '/')
buf = strconv.AppendBool(buf, false)
fmt.Println(string(buf)) // "true/false"

// AppendQuote
buf = buf[:0]
buf = strconv.AppendQuote(buf, "Hello\tWorld")
fmt.Println(string(buf)) // "Hello\tWorld" (with quotes)

The Append pattern is very useful when building HTTP responses, serializing data, or in other situations where you want to avoid unnecessary memory allocations in a hot path.


Comparison: strconv vs fmt #

This is a very common question: when should you use strconv and when fmt?

flowchart TD
    Q{"What do you\nwant to do?"} --> Conv["Type conversion\n(int↔string, float↔string, bool↔string)"]
    Q --> Rich["Complex formatting\n(multiple values, padding, width)"]
    Q --> Debug["Debugging or\nconsole logging"]
    Q --> Err["Creating errors\nwith context"]

    Conv --> C2{"How important\nis performance?"}
    C2 -- "Hot path / many calls" --> SC["strconv\n3-5x faster\nno extra allocations"]
    C2 -- "Ordinary" --> FMT["fmt.Sprintf\nmore readable"]

    Rich --> FMT2["fmt.Sprintf\n'%05d', '%-10s', etc."]
    Debug --> FMT3["fmt.Printf / fmt.Println"]
    Err --> FMT4["fmt.Errorf with %w"]

    style SC fill:#e8f5e9
    style FMT fill:#e3f2fd
    style FMT2 fill:#e3f2fd
    style FMT3 fill:#e3f2fd
    style FMT4 fill:#e3f2fd
import (
    "strconv"
    "fmt"
    "testing"
)

// A simple benchmark for illustration
func BenchmarkItoa(b *testing.B) {
    for i := 0; i < b.N; i++ {
        _ = strconv.Itoa(12345)
    }
}
// BenchmarkItoa: ~15 ns/op, 0 allocs/op

func BenchmarkSprintfInt(b *testing.B) {
    for i := 0; i < b.N; i++ {
        _ = fmt.Sprintf("%d", 12345)
    }
}
// BenchmarkSprintfInt: ~70 ns/op, 1 allocs/op

// Conclusion: strconv.Itoa is about 4-5x faster for int→string conversion

When Each Is More Appropriate #

// USE strconv for single conversions
id := 42
idStr := strconv.Itoa(id)            // ✓ fast, clear
idStr2 := fmt.Sprintf("%d", id)      // ✗ unnecessary overhead

price := 99.99
priceStr := strconv.FormatFloat(price, 'f', 2, 64) // ✓
priceStr2 := fmt.Sprintf("%.2f", price)             // ✗ for conversion only

// USE fmt for richer formatting
label := fmt.Sprintf("ID: %05d | Price: Rp%,.2f", id, price) // ✓ fmt is better suited
// strconv can't handle this in one call

// USE strconv for input parsing
func parseQueryParam(params url.Values) (*Filter, error) {
    filter := &Filter{}

    if pageStr := params.Get("page"); pageStr != "" {
        page, err := strconv.Atoi(pageStr)
        if err != nil {
            return nil, fmt.Errorf("invalid 'page' parameter: %w", err)
        }
        filter.Page = page
    }

    if limitStr := params.Get("limit"); limitStr != "" {
        limit, err := strconv.ParseInt(limitStr, 10, 32)
        if err != nil {
            return nil, fmt.Errorf("invalid 'limit' parameter: %w", err)
        }
        if limit < 1 || limit > 100 {
            return nil, fmt.Errorf("limit must be between 1-100, got: %d", limit)
        }
        filter.Limit = int(limit)
    }

    return filter, nil
}

Production Usage Patterns #

An HTTP Query Parameter Parser #

import (
    "fmt"
    "net/http"
    "strconv"
)

type PaginationParam struct {
    Page  int
    Limit int
    Order string
}

func parsePagination(r *http.Request) (*PaginationParam, error) {
    q := r.URL.Query()

    param := &PaginationParam{
        Page:  1,   // default
        Limit: 20,  // default
        Order: "asc",
    }

    if s := q.Get("page"); s != "" {
        page, err := strconv.Atoi(s)
        if err != nil || page < 1 {
            return nil, fmt.Errorf("invalid 'page' parameter: %q", s)
        }
        param.Page = page
    }

    if s := q.Get("limit"); s != "" {
        limit, err := strconv.Atoi(s)
        if err != nil || limit < 1 || limit > 100 {
            return nil, fmt.Errorf("invalid 'limit' parameter: %q (must be 1-100)", s)
        }
        param.Limit = limit
    }

    if s := q.Get("order"); s == "asc" || s == "desc" {
        param.Order = s
    }

    return param, nil
}

func listProductsHandler(w http.ResponseWriter, r *http.Request) {
    param, err := parsePagination(r)
    if err != nil {
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }
    // use param...
    fmt.Fprintf(w, "Page %d, Limit %d, Order %s\n",
        param.Page, param.Limit, param.Order)
}

Manual CSV Serialization #

import (
    "strings"
    "strconv"
)

type Product struct {
    ID    int
    Name  string
    Price float64
    Active bool
    Stock int
}

// Convert a product to a CSV row without extra libraries
func productToCSV(p Product) string {
    var sb strings.Builder

    sb.WriteString(strconv.Itoa(p.ID))
    sb.WriteByte(',')
    sb.WriteString(strconv.Quote(p.Name)) // handle names containing commas
    sb.WriteByte(',')
    sb.WriteString(strconv.FormatFloat(p.Price, 'f', 2, 64))
    sb.WriteByte(',')
    sb.WriteString(strconv.FormatBool(p.Active))
    sb.WriteByte(',')
    sb.WriteString(strconv.Itoa(p.Stock))

    return sb.String()
}

// Parse a CSV row back into a Product
func csvToProduct(row string) (Product, error) {
    parts := strings.SplitN(row, ",", 5)
    if len(parts) != 5 {
        return Product{}, fmt.Errorf("invalid CSV format: %q", row)
    }

    id, err := strconv.Atoi(parts[0])
    if err != nil {
        return Product{}, fmt.Errorf("invalid ID: %w", err)
    }

    name, err := strconv.Unquote(parts[1])
    if err != nil {
        name = parts[1] // fallback if there are no quotes
    }

    price, err := strconv.ParseFloat(parts[2], 64)
    if err != nil {
        return Product{}, fmt.Errorf("invalid price: %w", err)
    }

    active, err := strconv.ParseBool(parts[3])
    if err != nil {
        return Product{}, fmt.Errorf("invalid active status: %w", err)
    }

    stock, err := strconv.Atoi(parts[4])
    if err != nil {
        return Product{}, fmt.Errorf("invalid stock: %w", err)
    }

    return Product{
        ID:     id,
        Name:   name,
        Price:  price,
        Active: active,
        Stock:  stock,
    }, nil
}

Parsing Config from a .env File #

import (
    "bufio"
    "fmt"
    "os"
    "strconv"
    "strings"
)

type AppConfig struct {
    Port    int
    Debug   bool
    MaxConn int
    Timeout float64 // in seconds
    AppName string
}

func loadDotEnv(path string) (map[string]string, error) {
    f, err := os.Open(path)
    if err != nil {
        return nil, fmt.Errorf("loadDotEnv: %w", err)
    }
    defer f.Close()

    result := make(map[string]string)
    scanner := bufio.NewScanner(f)
    lineNumber := 0

    for scanner.Scan() {
        lineNumber++
        line := strings.TrimSpace(scanner.Text())

        // Skip empty lines and comments
        if line == "" || strings.HasPrefix(line, "#") {
            continue
        }

        parts := strings.SplitN(line, "=", 2)
        if len(parts) != 2 {
            return nil, fmt.Errorf("line %d: invalid format: %q", lineNumber, line)
        }

        key := strings.TrimSpace(parts[0])
        val := strings.TrimSpace(parts[1])
        // Remove quotes if present
        if unquoted, err := strconv.Unquote(val); err == nil {
            val = unquoted
        }

        result[key] = val
    }

    return result, scanner.Err()
}

func parseConfig(env map[string]string) (*AppConfig, error) {
    cfg := &AppConfig{
        Port:    8080,
        Debug:   false,
        MaxConn: 10,
        Timeout: 30.0,
        AppName: "MyApp",
    }

    if v, ok := env["PORT"]; ok {
        port, err := strconv.Atoi(v)
        if err != nil {
            return nil, fmt.Errorf("invalid PORT: %w", err)
        }
        if port < 1 || port > 65535 {
            return nil, fmt.Errorf("PORT must be 1-65535, got: %d", port)
        }
        cfg.Port = port
    }

    if v, ok := env["DEBUG"]; ok {
        debug, err := strconv.ParseBool(v)
        if err != nil {
            return nil, fmt.Errorf("DEBUG must be a boolean: %w", err)
        }
        cfg.Debug = debug
    }

    if v, ok := env["MAX_CONN"]; ok {
        maxConn, err := strconv.Atoi(v)
        if err != nil {
            return nil, fmt.Errorf("invalid MAX_CONN: %w", err)
        }
        cfg.MaxConn = maxConn
    }

    if v, ok := env["TIMEOUT"]; ok {
        timeout, err := strconv.ParseFloat(v, 64)
        if err != nil {
            return nil, fmt.Errorf("invalid TIMEOUT: %w", err)
        }
        cfg.Timeout = timeout
    }

    if v, ok := env["APP_NAME"]; ok && v != "" {
        cfg.AppName = v
    }

    return cfg, nil
}

When to Switch to Alternatives #

Keep using strconv if:
  ✓ Converting between strings and int, float, bool
  ✓ Parsing input from forms, query params, config files
  ✓ Serializing primitive values to strings for CSV or text formats
  ✓ Hot paths needing high-performance conversions
  ✓ Escaping and unescaping strings with Quote/Unquote

Consider fmt.Sprintf if:
  ✗ Richer formatting: padding, column widths, multiple values
  ✗ Code prioritizes readability over performance
  ✗ Formatting many values at once in one call

Consider encoding/json if:
  ✗ Converting structs to strings (JSON serialization)
  ✗ Complex data with nested structures
  ✗ Interoperability with APIs or other systems

Consider encoding/csv if:
  ✗ Reading or writing complex CSV files
  ✗ CSV with quoting, newlines inside fields, or complex escaping

Summary #

  • strconv.Atoi and strconv.Itoa are the most common shortcuts — direct conversions between string and int without string formatting overhead.
  • Always check the error from Parse functions* — a failed parse isn’t a panic, it returns a zero value and an error that must be handled explicitly.
  • *strconv.NumError has two types: ErrSyntax (wrong format) and ErrRange (valid number but out of range) — distinguish them for more informative error messages.
  • ParseInt with base 0 auto-detects the base from the prefix: 0x for hex, 0b for binary, 0 for octal — useful for input that may be in various formats.
  • FormatFloat with prec -1 produces the minimum representation that can be parsed back to the exact same value — use this for accurate float round-trips.
  • The Append* variants (AppendInt, AppendFloat, etc.) avoid new string allocations by appending directly to []byte — important for high-volume hot paths.
  • strconv is 3-5x faster than fmt.Sprintf for single type conversions — use strconv in HTTP handlers and frequently called loops.
  • strconv.Quote and strconv.Unquote are useful for logging and debugging strings that may contain invisible or special characters.
  • Validate after parsing — don’t just check the parse error, also validate the value range (e.g. port 1-65535, page > 0) before using the parsed value.

← Previous: Time   Next: Errors →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact