ValueError: invalid literal for int() with base 16 (tested fix)

ValueError: invalid literal for int() with base 16 means the string has something that isn't a hex digit, often a # from a colour code or a stray character. We tested 13 inputs in Python 3.13 to show exactly what int(s, 16) accepts.

1. What int(s, 16) accepts and rejects

for s in ["ff", "FF", "0xff", "0XFF", " ff\n", "#ff", "ff;", "0x", "", "g1", "ff_ff", "-0x1f", "ff.0"]:
    try:
        print(f"{s!r:10} -> {int(s, 16)}")
    except ValueError as e:
        print(f"{s!r:10} -> {type(e).__name__}: {e}")

Output:

'ff'       -> 255
'FF'       -> 255
'0xff'     -> 255
'0XFF'     -> 255
' ff\n'    -> 255
'#ff'      -> ValueError: invalid literal for int() with base 16: '#ff'
'ff;'      -> ValueError: invalid literal for int() with base 16: 'ff;'
'0x'       -> ValueError: invalid literal for int() with base 16: '0x'
''         -> ValueError: invalid literal for int() with base 16: ''
'g1'       -> ValueError: invalid literal for int() with base 16: 'g1'
'ff_ff'    -> 65535
'-0x1f'    -> -31
'ff.0'     -> ValueError: invalid literal for int() with base 16: 'ff.0'

A 0x prefix, upper or lower case, a sign, surrounding whitespace and underscores between digits are all fine. A #, trailing punctuation, a bare 0x, an empty string, a non-hex letter or a decimal point all raise. The message quotes the exact string, so read it for the stray character.

2. Parsing a #rrggbb colour

color = "#1e90ff"
try:
    r = int(color[0:2], 16)       # off by one: '#1'
except ValueError as e:
    print(type(e).__name__ + ":", e)
h = color.lstrip("#")
print(tuple(int(h[i:i + 2], 16) for i in (0, 2, 4)))
print(tuple(bytes.fromhex(h)))

Output:

ValueError: invalid literal for int() with base 16: '#1'
(30, 144, 255)
(30, 144, 255)

Slicing before stripping the # is the usual off-by-one. bytes.fromhex() splits a hex string into byte values in one call.

3. base 0: let the prefix decide

for s in ["0xff", "0o17", "0b101", "ff", "010"]:
    try:
        print(f"{s!r:7} base 0 -> {int(s, 0)}")
    except ValueError as e:
        print(f"{s!r:7} base 0 -> {type(e).__name__}: {e}")

Output:

'0xff'  base 0 -> 255
'0o17'  base 0 -> 15
'0b101' base 0 -> 5
'ff'    base 0 -> ValueError: invalid literal for int() with base 0: 'ff'
'010'   base 0 -> ValueError: invalid literal for int() with base 0: '010'

int(s, 0) reads 0x, 0o and 0b prefixes like Python source does, so bare hex like ff fails, and so does a leading zero like 010.

4. Forgetting the base gives the base 10 error

for s in ["ff", "1f", "0xff"]:
    try:
        print(f"{s!r:6} base 10 -> {int(s)}")
    except ValueError as e:
        print(f"{s!r:6} base 10 -> {type(e).__name__}: {e}")
for bad in (b"ff", bytearray(b"ff")):
    print(repr(bad), int(bad, 16))

Output:

'ff'   base 10 -> ValueError: invalid literal for int() with base 10: 'ff'
'1f'   base 10 -> ValueError: invalid literal for int() with base 10: '1f'
'0xff' base 10 -> ValueError: invalid literal for int() with base 10: '0xff'
b'ff' 255
bytearray(b'ff') 255

Without a second argument, int() parses base 10, so hex text gives with base 10 in the message. bytes and bytearray are also accepted with a base. Related: invalid literal for int() with base 10 and could not convert string to float.

Frequently asked questions

Does int(s, 16) accept the 0x prefix?

Yes. int('0xff', 16) is 255, but a bare '0x' with no digits raises.

How do I convert a hex colour like #1e90ff?

Strip the # first: h = s.lstrip('#'), then int(h[0:2], 16) for each pair, or tuple(bytes.fromhex(h)).

What does int(s, 0) do?

It uses the prefix to pick the base: 0x hex, 0o octal, 0b binary, otherwise decimal. Bare 'ff' fails.

Written with AI assistance. Measured on 2026-10-22 on Debian with Python 3.13.5: 13 inputs to int(s, 16), #rrggbb parsing with slices and bytes.fromhex, int(s, 0) prefixes, base 10 on hex text, and bytes input. Every block was run verbatim and its output pasted unedited. PlainInspect is our extension; Python is free.