How to Convert String to Int in Go

Use `strconv.Atoi` for simple base-10 conversion or `strconv.ParseInt` when you need to specify the bit size or handle different bases.

The string that isn't a number

You are reading a configuration file. The value for max_retries arrives as "5". You need 5 to pass to a loop counter. In Python or JavaScript, the runtime quietly coerces the text into a number behind the scenes. Go refuses. It treats the string as a sequence of UTF-8 bytes and the integer as a fixed-width binary value. They occupy different memory layouts and serve different purposes. Converting between them requires a deliberate step that validates the input, calculates the mathematical value, and allocates the correct type.

Go draws a hard line between text and numbers. This design prevents silent data corruption. A typo like "5a" or an overflow like "99999999999999999" will never silently truncate to zero. You have to parse. Parsing means scanning the byte sequence, verifying every character belongs to the expected numeric set, handling optional signs, calculating the value, and checking that the result fits inside the target type. If any step fails, the function stops and hands you an error.

How parsing actually works

Think of parsing like a bouncer checking IDs at a venue. The string is the ID card. The parser checks if it is valid, matches the venue rules, and fits the capacity limit. If the card is forged or expired, the bouncer turns you away. The venue does not guess your age or let you in with a blank card.

Under the hood, strconv functions read the byte slice backing the string. They skip leading whitespace. They check for a + or - sign. They iterate through each byte, verify it falls within the valid digit range for the chosen base, and multiply the accumulator by the base before adding the new digit. The parser tracks the sign and applies it at the end. It also maintains a running check against the maximum and minimum values allowed by the target bit width. If the accumulator exceeds the ceiling or drops below the floor, the function aborts and returns an overflow error.

This explicit contract keeps your data pipelines predictable. You never get a partially parsed number. You get either a complete, validated integer or a clear error explaining why the conversion failed.

The minimal path: strconv.Atoi

The standard library provides strconv.Atoi for everyday base-10 conversion. The name stands for ASCII to Integer. It returns two values: the parsed int and an error. The compiler forces you to acknowledge both. You cannot accidentally drop the error without explicitly discarding it.

Here is the simplest way to convert a command-line flag into a usable integer:

package main

import (
	"fmt"
	"os"
	"strconv"
)

// main runs the program and parses the first CLI argument as a base-10 integer.
func main() {
	// Check that the user actually provided an argument
	if len(os.Args) < 2 {
		fmt.Println("Usage: go run main.go <number>")
		return
	}

	// Atoi scans the string, validates digits, and returns an architecture-dependent int
	val, err := strconv.Atoi(os.Args[1])
	// The compiler requires handling the error before using val
	if err != nil {
		fmt.Printf("Invalid number: %v\n", err)
		return
	}

	// val is now safe to use in arithmetic or control flow
	fmt.Printf("Converted value: %d\n", val)
}

The int type itself is architecture-dependent. On a 64-bit machine, it handles values up to 9 quintillion. On a 32-bit machine, it caps at 2 billion. Atoi matches the native word size, which makes it fast and idiomatic for general purpose code. If you pass "8080", the parser multiplies and adds until it reaches the end. If you pass "808a", it stops at a and returns a *strconv.NumError containing the original string and a syntax error.

Trust the error return. Zero is a valid number, but a zero from a failed parse is a bug waiting to happen.

When you need control: strconv.ParseInt

Real systems rarely deal with plain base-10 decimals. Configuration files use hex colors. APIs send timestamps as strings. You need to lock down the bit size or change the base. strconv.ParseInt takes three arguments: the string, the base, and the bit size. Base 0 auto-detects prefixes like 0x or 0b. Base 10 forces decimal. The bit size argument tells the parser how many bits to reserve.

Here is how you handle hexadecimal input and enforce a 64-bit ceiling:

package main

import (
	"fmt"
	"strconv"
)

// main demonstrates parsing hex and large decimals with explicit bit sizing.
func main() {
	// Base 16 expects 0-9 and a-f. The parser validates each character against this set
	hexStr := "1A"
	val, err := strconv.ParseInt(hexStr, 16, 64)
	// ParseInt returns an int64, not an architecture-dependent int
	if err != nil {
		fmt.Printf("Parse error: %v\n", err)
		return
	}

	// The value is now a guaranteed 64-bit signed integer
	fmt.Printf("Hex %s as int64: %d\n", hexStr, val)

	// Base 10 forces decimal interpretation. Bit size 64 caps the range
	largeStr := "9223372036854775807"
	bigVal, err := strconv.ParseInt(largeStr, 10, 64)
	// If the string exceeds int64 max, err will be an overflow error
	if err != nil {
		fmt.Printf("Overflow or invalid: %v\n", err)
	} else {
		fmt.Printf("Large int: %d\n", bigVal)
	}
}

The bit size parameter acts as a hard ceiling. If you request 32 but the string represents a value that requires 33 bits, the function returns an overflow error instead of truncating. This explicit contract keeps your data pipelines predictable. You never get a silently wrapped value.

Go functions that parse or allocate almost always return (T, error). The community embraces the if err != nil boilerplate because it forces you to handle the unhappy path at the call site. You will see it everywhere. Write it without hesitation.

Specify the base and bit size explicitly. Auto-detection is convenient until a stray 0x prefix breaks your decimal parser.

Pitfalls and compiler reality

What happens when you try to cheat? Go's compiler rejects implicit conversions. If you write port := int("8080"), the build fails immediately with cannot convert "8080" (untyped string constant) to type int. The language refuses to guess. You must use strconv.

Another trap is ignoring the error. You can technically write port, _ := strconv.Atoi(s), but that discards the validation step. If the input is "abc", port becomes 0. Your server binds to localhost:0, the OS picks a random port, and debugging turns into a nightmare. The underscore is for when you genuinely do not care about a return value. You always care about parse errors. Use it sparingly with errors, and never with parsing functions.

Runtime panics do not happen here. strconv functions are safe. They return errors for invalid syntax, overflow, or out-of-range values. The only way to panic is to force a type assertion or index out of bounds elsewhere. The error type is *strconv.NumError, which contains the function name, the original string, and the underlying error. You can inspect it if you need to distinguish between a syntax error and an overflow.

Never swallow a parse error. Log it, fail fast, or return it. Silent zeros are the enemy of reliable software.

Decision matrix

Use strconv.Atoi when you expect standard base-10 input and want the native architecture int type. Use strconv.ParseInt when you need to parse hexadecimal, binary, or octal strings, or when you must guarantee a specific bit width like int64. Use strconv.ParseFloat when your input contains decimal points or scientific notation. Use manual byte slicing when you are processing high-throughput streams and want to avoid allocation overhead. Use fmt.Sscanf when you are parsing mixed text and numbers in a single line, like log entries or CSV rows.

Pick the parser that matches your input contract. Explicit types prevent silent truncation.

Where to go next