Why does int('3.0') raise ValueError while int(3.0) returns 3?
answer
- One name, two completely different jobs
- Text is parsed, numbers are converted
- The literal grammar has no decimal point
- Toward zero, not down, not nearest
- float() first, then int(), and know the cost
basics
~20 sint() parses text with the integer grammar only, so the dot in '3.0' makes it a ValueError. Given a float object it converts numerically, truncating toward zero. For decimal text, call float() first and then int().
solid answer
~40 s`int()` does two unrelated jobs. Given a **string** it is a parser: the text must match the integer literal grammar for the base - optional whitespace, an optional sign, digits with single underscores between them - so `'3.0'`, `'3e2'`, `''` and `'1__0'` all raise `ValueError`. Given a **number** it is a converter: `int(3.9)` is 3 and `int(-3.9)` is -3, truncating toward zero rather than rounding or flooring. That is why text like `'3.0'` needs `int(float(s))`, which deliberately discards the fraction - and also silently accepts `'3.9'`, so validate with `float.is_integer()` if a whole number is actually required. `int(float('nan'))` raises `ValueError` and `int(float('inf'))` raises `OverflowError`.
code
python · 7 linesprint(int(3.9), int(-3.9))
print(int("42"), int(" 42 "), int("1_000"))
try:
int("3.0")
except ValueError as e:
print("ValueError:", e)
print(int(float("3.0")))go deeper
Be ready to state the rule in one breath: int() on a string demands whole-number text, int() on a float chops the fraction off. Have int(float(s)) ready as the fix for '3.0' and know that the error type is ValueError.
Explain the mechanics: the accepted string grammar (sign, digits, underscores between digits, surrounding whitespace) and truncation toward zero, demonstrated on a negative number. Name what int(float(s)) costs you.
Show the input-handling judgement: catch ValueError at the parse boundary and turn it into a domain rejection, validate with float.is_integer() when truncation would be a data error, and parse long whole-number text with int() directly to avoid float precision loss.
Own the policy question - where in a system numeric text is converted, and whether coercion or rejection is the house rule. Silent truncation at a boundary is a decision about data quality, and it belongs in a shared parsing layer rather than scattered through call sites.
### Two different jobs behind one name `int()` is a constructor whose behaviour depends entirely on the *type* of what you hand it, and the two behaviours have almost nothing in common. Confusing them is what makes `int('3.0')` surprising. **Given a string, `int()` is a parser.** It accepts exactly the grammar of an integer literal in the requested base and nothing else: optional surrounding whitespace, an optional `+` or `-` sign, then one or more digits with optional single underscores *between* digits. There is no fractional part in that grammar and no exponent part. So all of these raise `ValueError` with the message `invalid literal for int() with base 10: '...'`: ```python int("3.0") # a dot is not part of the integer grammar int("3e2") # nor is an exponent int("") # no digits at all int("1__0") # doubled underscore int(" 4 2") # embedded whitespace ``` The strictness is deliberate. `int(s)` is the function that asserts "this text is a whole number"; if it quietly accepted `'3.9'` and returned `3`, a data-entry error would become a silently wrong answer instead of a loud failure. The things it *does* forgive are cosmetic only: leading and trailing whitespace (`int(" 42\n")` is 42), a sign, and PEP 515 digit separators (`int("1_000")` is 1000, though `int("_1000")` is not). `bytes` and `bytearray` are accepted on the same terms, so `int(b"42")` works. **Given a number, `int()` is a converter.** `int(3.9)` is `3` and `int(-3.9)` is `-3`. It **truncates toward zero**: it discards the fractional part rather than rounding to nearest and rather than flooring. The distinction is invisible on positive values and bites on negative ones, which is where interviewers probe. Two float values have no integer to convert to, and each has its own error: `int(float("nan"))` raises `ValueError` and `int(float("inf"))` raises `OverflowError`. For a class of your own, `int()` looks for `__index__` first (the lossless, "I *am* an integer" protocol) and falls back to `__int__`. ### The practical consequence: parsing user text Because the string path has no notion of a decimal point, text like `"3.0"` has to be routed through the float parser first: ```python value = int(float(user_text)) ``` That composition is honest about what it does — `float()` parses decimal text, `int()` throws the fraction away — but it carries two costs a senior candidate should name. First, it also accepts `"3.9"` and yields `3`. If the requirement is really "must be a whole number", validate rather than truncate: parse with `float()`, check `float.is_integer()`, and reject otherwise. Truncation is a decision; make it on purpose. Second, it loses precision on long numerals. A binary64 float carries roughly 15-17 significant decimal digits, so `int(float("12345678901234567890"))` is *not* the number that was typed, while `int("12345678901234567890")` is exact — Python's `int` is arbitrary precision, and the string path never goes through a float. When the text is known to be a whole number, always parse it with `int()` directly; the `float()` detour is only for text that genuinely carries a decimal point. ### How to fail well `int()` raises `ValueError` and nothing else for malformed text, so a boundary that parses untrusted input has an easy shape: ```python try: quantity = int(field) except ValueError: reject(f"expected a whole number, got {len(field)} characters") ``` Catch `ValueError` specifically at the point of parsing, convert it into a domain-level rejection, and avoid logging the raw value if it is untrusted. A blanket `except Exception` around a conversion is a red flag: it swallows `TypeError` (which means you passed the wrong *type*, a programming bug, not bad data) and, on very long digit strings, the `ValueError` that CPython raises for a different reason entirely. ### What an interviewer is listening for The good answer states the parser/converter split in one sentence, gives the truncation-toward-zero rule with a negative example, and reaches for `int(float(s))` while naming its two costs. The weak answer says "`int()` rounds" — it does not — or claims `int('3.0')` returns 3 because "Python is dynamically typed", which confuses type flexibility with parsing rules.
- Can int() ever fail on a float argument rather than truncating?Yes, for the two non-finite values. `int(float('nan'))` raises `ValueError` because NaN has no integer counterpart, and `int(float('inf'))` raises `OverflowError` because the value is unbounded. Every finite float converts, however large - `int` is arbitrary precision, so even a float near the top of the double range yields an exact integer.
- Why is int(float(s)) a poor way to parse a twenty-digit numeric string?Because the value passes through a binary64 float, which carries only about 15-17 significant decimal digits. `int(float('12345678901234567890'))` returns a nearby but different number, silently. When the text is known to be a whole number, parse it with `int(s)` directly - that path never touches a float and is exact at any length.
- How does int() decide what to do with an instance of your own class?It looks for `__index__` first, the protocol that says the object genuinely *is* an integer, and falls back to `__int__` for lossy numeric conversion. Implementing `__index__` also makes the object usable as a sequence index and in `hex()`; implementing only `__int__` does not.
Handing int() a string is like showing a passport control officer a document: it either matches the accepted format or you are turned away. Handing it a float is like handing over cash and being given only the whole notes back.
saying these in an interview costs you the question
- Says int('3.0') returns 3 because int() rounds the text
- Claims int() rounds to nearest rather than truncating toward zero
- Answers that int(-3.9) is -4
- Thinks int(' 42 ') fails because of the whitespace
- Treats int(float(s)) as exact for long digit strings
- Wraps the conversion in a bare except instead of catching ValueError