Python: ValueError: invalid literal for int() with base 10

Quick answer

  • The quoted value is a repr of exactly what int() got — read it literally, invisible characters included.
  • Message ends in : ''? An empty string — find the blank field, not the conversion.
  • Decimal like '196.41'? Go through float: int(float(s)).
  • You do not need .strip()int() already ignores surrounding whitespace.

The exact error string

Traceback (most recent call last):
  File "app.py", line 2, in <module>
    qty = int(row["qty"])
ValueError: invalid literal for int() with base 10: ''

# the same error, different inputs — the value after the colon is always the real input:
ValueError: invalid literal for int() with base 10: 'abc'
ValueError: invalid literal for int() with base 10: '196.41'
ValueError: invalid literal for int() with base 10: '1,234'
ValueError: invalid literal for int() with base 10: '42​'

Python raises this when int() is handed a string it cannot read as a whole number in the requested base. The type was fine — a string is a perfectly acceptable argument — but the contents could not be parsed.

Everything below was verified on CPython 3.14.6.

Anatomy of the message

invalid literal for int() with base 10: '196.41'
                             ^^^^^^^^   ^^^^^^^^
                             |          |
                             |          +-- repr() of the exact string received
                             +------------- the base argument, which defaults to 10

Two things are easy to skim past, and both save time:

Fix 1: the empty string

This form dominates real-world reports — the Stack Overflow question for invalid literal for int() with base 10: '' has over four million views, more than every other variant of this error combined. When the message ends in : '' there is nothing wrong with your conversion; you were handed a blank. It almost never comes from a literal in your code, so chase the source:

# consecutive delimiters produce an empty field
'1,2,,4'.split(',')        # ['1', '2', '', '4']   <- index 2 is ''

# a trailing newline produces an empty final line
'a\n\n'.splitlines()       # ['a', '']

# splitting an empty string does NOT give an empty list
''.split(',')              # ['']  <- one empty string, so the loop still runs once

The usual culprits are a blank CSV cell, a header row being parsed as data, an unfilled form field, a missing environment variable that defaulted to '', or a file ending with a newline. Handle it explicitly at the boundary:

raw = row.get("qty", "")
qty = int(raw) if raw.strip() else 0      # decide what a blank MEANS

# reading a file — skip blanks rather than letting them reach int()
for line in f:
    line = line.strip()
    if not line:
        continue
    process(int(line))

Decide deliberately whether a blank means zero, means "unknown", or means the row should be rejected. Silently defaulting to 0 is a common way to turn a data problem into a wrong number.

Fix 2: you don't need .strip()

Most guides on this error show .strip() as a required step. It isn't, and believing otherwise sends you looking in the wrong place:

int(' 42 ')        # 42   — surrounding spaces are ignored
int('42\n')        # 42   — trailing newline is ignored
int('\t42\r\n')    # 42   — tabs and CRLF too

int('4 2')         # ❌ ValueError — the space is INSIDE the number

So if your value only has whitespace around the edges, it already works and something else is wrong. Reach for .strip() only when you want to detect a blank (as in Fix 1), not to make a conversion succeed.

Fix 3: decimals, separators and exponents

int() parses integers, and will not round or reinterpret for you. Before reaching for a conversion, decide whether the value is genuinely an integer. If '196.41' is a price, a measurement or a rate, the right type is float (or Decimal for money) — forcing it through int() discards the fractional part permanently:

price = float('196.41')        # ✅ 196.41 — keep the precision you were given

# only if your application genuinely requires a whole number:
int(float('196.41'))           # 196  — truncates toward zero, .41 is GONE
round(float('196.41'))         # 196  — nearest integer, different rule

from decimal import Decimal
Decimal('196.41')              # ✅ exact, for money
InputResultWhat to do
int('196.41')❌ ValueErrorUsually float(s). Truncate only if you mean to
int('1e3')❌ ValueErrorfloat(s) → 1000.0; int(float(s)) if a whole number is required
int('1,234')❌ ValueErrorint(s.replace(',', ''))
int('1_000')✅ 1000Underscores are legal (PEP 515, Python 3.6+)
int('+42'), int('-42')✅ 42, -42Signs are fine — but '- 42' is not

The underscore row surprises people: int('1_000') returns 1000, though '_100' and '100_' both fail because an underscore may only sit between digits. For thousands separators, strip them explicitly — and if the data is locale-formatted (a comma as the decimal point, as in much of Europe) do the locale conversion deliberately rather than deleting characters and hoping.

Fix 4: it looks like a number but isn't

When the value in the message looks perfectly valid, compare its length against what you expect. The error's repr already reveals the culprit if you read the escapes:

print(repr(value), len(value))
# '42​' 3     <- three characters, not two

Which invisible characters are fatal is not what most people assume, so this is worth knowing precisely:

Characterint('42' + c)Why
Non-breaking space U+00A0✅ 42Python classes it as whitespace, so it is stripped
Zero-width space U+200B❌ ValueErrorNot whitespace — it is a real character
Byte order mark U+FEFF❌ ValueErrorCommon at the start of a UTF-8 file
RTL mark U+200F❌ ValueErrorArrives with copied bidirectional text

The BOM case is the one to watch in data pipelines, because a UTF-8 file may begin with  and only the very first field of the very first row will fail — every other row converts cleanly, which makes it look like a data problem rather than an encoding one. Read such files with encoding='utf-8-sig', which consumes the mark for you. Our CSV to JSON converter strips it automatically for the same reason.

Fix 5: when the text isn't base 10

If your string is hexadecimal, binary or octal, the fix is to tell int() the base rather than to clean the string:

int('ff')           # ❌ ValueError — 'f' is not a base-10 digit
int('ff', 16)       # 255
int('0xff', 16)     # 255  — the 0x prefix is allowed when the base matches
int('1010', 2)      # 10

int('0xff', 0)      # 255  — base 0 means "infer from the prefix"

Base 0 is the useful one for config files and user input that may arrive in any notation: it reads 0x, 0o and 0b prefixes and falls back to decimal.

Don't validate with isdigit()

This is the most commonly recommended pre-check, and it is wrong in both directions — it rejects valid input and accepts invalid input:

Valueisdigit()int()Verdict
'-5'False✅ -5False negative — rejects a valid number
' 42 'False✅ 42False negative
'1_000'False✅ 1000False negative
'²'True❌ ValueErrorFalse positive — passes the check, then crashes
'٣' (Arabic-Indic)True✅ 3Correct, but probably not what you intended

The only reliable test is the conversion itself:

def to_int(s, default=None):
    try:
        return int(s)
    except (ValueError, TypeError):     # TypeError catches None and non-strings
        return default

That last row is worth a thought for anything user-facing: int('٣') returns 3 because Python accepts any Unicode decimal digit, not just ASCII. If you need ASCII-only input, check explicitly rather than relying on the conversion to reject it.

ValueError or TypeError?

CallException
int('abc')ValueError: invalid literal for int() with base 10: 'abc'
int(None)TypeError: int() argument must be a string, a bytes-like object or a real number, not 'NoneType'
int([])TypeError: ... not 'list'

ValueError means the type was acceptable but the contents weren't; TypeError means the argument was never parseable to begin with. A bare except ValueError around a conversion will let a None straight through, which is why the helper above catches both. A None arriving here usually has the same origin as unsupported operand type(s) — a function that returned nothing.

Debugging checklist

Frequently Asked Questions

What does invalid literal for int() with base 10 actually mean?

It means int() received a string that is not a valid whole number written in base 10. The phrase "base 10" is not decoration — it is the second argument to int(), which defaults to 10, so the message is telling you which number system it tried to read the text in. The value printed after the colon is the exact string it received, shown as a Python repr, so quotes, spaces and invisible characters all appear in it.

Why does int('') fail with an empty string?

Because an empty string contains no digits at all, and int() has nothing to parse. This is the most reported form of the error and it almost never comes from a literal empty string in your code — it comes from a blank CSV field, a trailing newline producing an empty final line, an unfilled form input, or splitting a string on a delimiter that appears twice in a row. Find the source of the blank rather than wrapping the conversion in a try block.

Do I need to call .strip() before int()?

No. int() already ignores surrounding whitespace, so int(' 42 ') and int('42\n') both return 42 without any help. Many guides show .strip() as a required step, which is simply wrong on any modern Python. Whitespace inside the number is a different matter — int('4 2') genuinely fails, and .strip() will not rescue that because the space is not at either end.

Is isdigit() a safe way to check before converting?

No, and it is wrong in both directions. '-5'.isdigit() is False even though int('-5') works, so you would reject valid input; and '²'.isdigit() is True even though int('²') raises, so you would accept invalid input. The same applies to ' 42 ' and '1_000', both of which convert fine while isdigit() reports False. The reliable check is to attempt the conversion inside try/except ValueError.

Why does int('196.41') fail when it is clearly a number?

Because int() parses integers only, and a decimal point is not part of an integer literal. First decide whether the value really is a whole number: if it is a price, measurement or rate then float(s) is the correct conversion, and Decimal(s) is better for money. Only if your application genuinely requires an integer should you go through int(float(s)), which truncates toward zero and silently discards the fractional part, or round(float(s)) for nearest-integer behaviour.

The string looks like a number but still fails. What now?

Print repr(value) and compare its length to what you expect. The error message already does this for you — it shows the value as a repr, so a zero-width space appears as and a byte order mark as . Those characters are invisible in a terminal but fatal to int(). Interestingly a non-breaking space is not fatal, because Python treats it as whitespace and strips it, which is why some invisible characters break the conversion and others do not.

What is the difference between this and TypeError from int(None)?

ValueError means int() understood the type it was given but could not read the contents, whereas TypeError means the argument was the wrong type entirely. int('abc') raises ValueError: invalid literal for int() with base 10, while int(None) raises TypeError: int() argument must be a string, a bytes-like object or a real number, not 'NoneType'. If you are catching one, decide deliberately whether you need to catch the other too.

References

Data arriving as strings?

The CSV converter strips the BOM and can infer real numbers, so blanks and hidden characters never reach your int() call.

CSV to JSON Error Log Analyzer All Error References
About the author

Pasindu Ishan is a software developer based in Sri Lanka. He builds privacy-first developer tools at JSON Dev Tools.