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:#fce4ecInteger 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:#ffebeeimport (
"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:#e3f2fdimport (
"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.Atoiandstrconv.Itoaare the most common shortcuts — direct conversions betweenstringandintwithout 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.NumErrorhas two types:ErrSyntax(wrong format) andErrRange(valid number but out of range) — distinguish them for more informative error messages.ParseIntwithbase 0auto-detects the base from the prefix:0xfor hex,0bfor binary,0for octal — useful for input that may be in various formats.FormatFloatwithprec -1produces 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.strconvis 3-5x faster thanfmt.Sprintffor single type conversions — usestrconvin HTTP handlers and frequently called loops.strconv.Quoteandstrconv.Unquoteare 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.