skip to content

In strconv.ParseInt, what do the base and bitSize arguments control?

level: middleimportance: should knowfreq 54%

answer

  1. two numbers, two unrelated jobs
  2. the radix, and how wide the answer may be
  3. 0 means read the prefix yourself
  4. the return type never changes with the width

basics

~20 s

base is the radix, 2 through 36, or 0 to infer it from a 0x, 0o, 0b or leading-zero prefix. bitSize says which signed width the value must fit: 0, 8, 16, 32 or 64. The result is always an int64.

solid answer

~50 s

The signature is `func ParseInt(s string, base int, bitSize int) (int64, error)`. `base` is the radix; it must be 0 or between 2 and 36. Base 0 is special: the prefix decides, so `0x`/`0X` is hex, `0b` binary, `0o` octal, a bare leading `0` is octal too, and underscore digit separators such as `1_000` are accepted *only* at base 0. With an explicit base the prefix is not stripped, so `ParseInt("0x1f", 16, 64)` is a syntax error. `bitSize` is a range check, not a return type: 0 means the value must fit `int`, and 8, 16, 32 and 64 mean the corresponding signed types. A value too large gives `strconv.ErrRange` together with the clamped maximum-magnitude value for that width. Doing the range check at the parse step is why `strconv.ParseUint(v, 10, 16)` is the neat way to accept a TCP port.

code

go · 4 lines
go
strconv.ParseInt("0x1f", 0, 64)  // 31, nil: prefix decides the base
strconv.ParseInt("0x1f", 16, 64) // 0, ErrSyntax: x is not a hex digit
strconv.ParseInt("1_000", 0, 64) // 1000, nil: separators need base 0
strconv.ParseInt("300", 10, 8)   // 127, ErrRange: clamped to int8's max

go deeper

for a junior

Know that ParseInt exists for cases Atoi cannot handle: other bases and a width limit. Be able to say that the two extra arguments are the radix and the target size, and that the result comes back as an int64.

for a middle

This is your level's question. Explain base 0 prefix inference, the underscore rule that goes with it, bitSize as a range check rather than a type, and the fact that an out-of-range parse returns the clamped maximum alongside ErrRange.

for a senior

Show how you use bitSize as validation instead of writing bounds checks after the fact, and explain what goes wrong operationally when the range error is ignored: a saturated setting reads as a plausible number rather than an obvious zero.

for a principal

Set the house rule for configuration parsing: explicit base 10 for human-written values so a zero-padded string never silently becomes octal, widths chosen to match the domain, and a single helper so every service reports a bad setting the same way at startup.

## The signature ```go func ParseInt(s string, base int, bitSize int) (i int64, err error) ``` Three inputs, two outputs, and the two integer inputs do completely different jobs. Candidates who have only ever used `strconv.Atoi` tend to guess that `bitSize` changes the return type; it does not. `ParseInt` always returns an `int64`. ## base: the radix, or 0 for "read the prefix" `base` must be 0, or a value from 2 through 36. Digits above 9 are the letters `a` to `z`, case-insensitively, so base 36 gives you `z` = 35. Base 0 means *infer the base from the string's own prefix*, exactly the way a Go source literal would be read: | input | base 0 result | | --- | --- | | `"42"` | 42, decimal | | `"0x2a"` or `"0X2A"` | 42, hexadecimal | | `"0b101010"` | 42, binary | | `"0o52"` | 42, octal | | `"052"` | 42, legacy octal from the bare leading zero | | `"1_000"` | 1000, underscores accepted | Two consequences catch people out. First, **underscore separators are permitted only when base is 0** - `ParseInt("1_000", 10, 64)` is a syntax error. Second, **an explicit base does not strip the prefix**: at base 16 the character `x` in `"0x1f"` is not a hex digit, so you get `strconv.ErrSyntax`. Strip the prefix yourself or pass base 0. And a config value like `"010"` read at base 0 quietly means 8, which is a real source of surprise when someone zero-pads a setting for alignment - if a human writes the value, prefer an explicit base 10. An out-of-domain base such as 1 or 37 returns a `*strconv.NumError` whose message says the base is invalid, rather than panicking. ## bitSize: a range check, not a type `bitSize` must be 0 or one of 8, 16, 32, 64, naming the signed type the parsed value has to fit: `int8`, `int16`, `int32`, `int64`, with 0 meaning `int`. The result still comes back as `int64`; you convert down yourself, and that conversion is safe precisely because the range has already been validated: ```go v, err := strconv.ParseInt(s, 10, 16) if err != nil { return err } var level int16 = int16(v) // cannot overflow: ParseInt already checked ``` When the digits are valid but the number does not fit, `ParseInt` does something worth remembering: it returns `strconv.ErrRange` **and** the maximum-magnitude value of that width and sign. `ParseInt("300", 10, 8)` gives `127` and an error. It does not truncate to the low bits (`300 & 0xFF` would be 44) and it does not return zero. Ignore the error and you get a saturated value, not an obviously wrong one. ## Using bitSize as validation Because the width check happens inside the parse, `bitSize` is often the cheapest bounds check you can write. A configuration loader reading a TCP port can say: ```go port, err := strconv.ParseUint(v, 10, 16) // 0..65535, checked at parse time ``` and skip a separate `if port > 65535` entirely. `ParseUint` has the same shape as `ParseInt` and rejects a leading `-` outright. This is the pattern to reach for when an environment string has a natural width: a port, a byte value, a percentage stored in an `int8`. ## How Atoi relates `strconv.Atoi(s)` is `ParseInt(s, 10, 0)` with a fast path for short strings and with the returned error's `Func` field renamed to `"Atoi"`. That is why Atoi rejects `1_000` and `0x1f`: base 10 is fixed, so neither separators nor prefixes apply. Reach for `ParseInt` when you need a non-decimal base, a width limit, or an `int64` result on a 32-bit platform; reach for `Atoi` for the plain decimal case, because it is shorter and returns an `int` directly. ## The whole family `ParseInt`, `ParseUint`, `ParseFloat` and `ParseBool` all take the string first and all return `(value, error)` with the same `*strconv.NumError` shape on failure. `ParseUint` takes the same base and bitSize arguments with unsigned widths; `ParseBool` takes neither. ## Answering well A good answer names both arguments in one sentence, then adds the two facts that separate use from understanding: base 0 means prefix inference and is the only base that accepts underscores, and bitSize is a range check whose failure returns a clamped value rather than zero. Mentioning that the return type is always `int64` regardless of bitSize usually settles the question.

  • Why does strconv.ParseInt return an int64 even when bitSize is 8?
    One signature has to cover every width, so the widest type is the return type and `bitSize` is a validation parameter rather than a type parameter. You convert down yourself with `int8(v)`, and that conversion cannot overflow because ParseInt already rejected anything out of range.
  • What is strconv.Atoi in terms of ParseInt?
    `Atoi(s)` is `ParseInt(s, 10, 0)` with a fast path for short strings and the error's `Func` field set to `"Atoi"`. Because the base is fixed at 10 it accepts no `0x` prefix and no underscore separators, and because it returns an `int` you skip the int64 conversion.
  • How would you parse a TCP port from an environment variable in one call?
    `strconv.ParseUint(v, 10, 16)`. The 16-bit width makes 65535 the maximum, so anything larger comes back as `strconv.ErrRange` at the parse step and a leading minus is rejected as invalid syntax. No separate bounds check is needed afterwards.
  • What happens if you pass a base of 1 or 37?
    You get a `*strconv.NumError` whose `Err` reports an invalid base, not a panic. Valid bases are 0 and 2 through 36, where digit values above 9 are spelled with the letters a through z, case-insensitively.

saying these in an interview costs you the question

  • Says base 0 means decimal
  • Thinks bitSize changes the returned type
  • Believes an out-of-range value is truncated to the low bits
  • Passes base 16 for a string that still carries its 0x prefix
  • Assumes underscore separators work at any base
  • Converts the int64 down without checking the error first