Quick answer
- The quoted value is a
reprof exactly whatint()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:
- "base 10" is not boilerplate. It is the second parameter of
int(). Seeing it confirms nobody passed a different base, and it hints at the fix when your text really is hexadecimal or binary — see Fix 5. - The value is a
repr, not the raw text. That is a gift: a trailing newline shows as'42\n', a zero-width space as'42', and an empty string as''. Python is already showing you the invisible characters — most readers just don't realise the quotes and backslashes are meaningful.
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
| Input | Result | What to do |
|---|---|---|
int('196.41') | ❌ ValueError | Usually float(s). Truncate only if you mean to |
int('1e3') | ❌ ValueError | float(s) → 1000.0; int(float(s)) if a whole number is required |
int('1,234') | ❌ ValueError | int(s.replace(',', '')) |
int('1_000') | ✅ 1000 | Underscores are legal (PEP 515, Python 3.6+) |
int('+42'), int('-42') | ✅ 42, -42 | Signs 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:
| Character | int('42' + c) | Why |
|---|---|---|
Non-breaking space U+00A0 | ✅ 42 | Python classes it as whitespace, so it is stripped |
Zero-width space U+200B | ❌ ValueError | Not whitespace — it is a real character |
Byte order mark U+FEFF | ❌ ValueError | Common at the start of a UTF-8 file |
RTL mark U+200F | ❌ ValueError | Arrives 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:
| Value | isdigit() | int() | Verdict |
|---|---|---|---|
'-5' | False | ✅ -5 | False negative — rejects a valid number |
' 42 ' | False | ✅ 42 | False negative |
'1_000' | False | ✅ 1000 | False negative |
'²' | True | ❌ ValueError | False positive — passes the check, then crashes |
'٣' (Arabic-Indic) | True | ✅ 3 | Correct, 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?
| Call | Exception |
|---|---|
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
- ✓ Read the quoted value as a
repr— escapes in it are real characters - ✓ Ends in
: ''? Find the blank field, don't patch the conversion - ✓ Compare
len(value)against the digits you can see - ✓ Don't add
.strip()hoping it helps —int()already ignores outer whitespace - ✓ Decimal or exponent? Go through
float()first - ✓ CSV from Excel failing on row 1 only? Read it with
encoding='utf-8-sig' - ✓ Hex or binary? Pass the base —
int(s, 16)orint(s, 0) - ✓ Never gate the conversion on
isdigit(); usetry/except
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
- int() (Python Standard Library)
- str.isdigit (Python Standard Library)
- PEP 515 — Underscores in Numeric Literals (Python 3.6)
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.