skip to content

When does Python raise ValueError rather than TypeError?

level: juniorimportance: must knowfreq 62%

answer

  1. Two questions: what kind, or what content
  2. One is about the object's type
  3. The other says the value is unusable
  4. int of a list versus int of 'twelve'
  5. KeyError and IndexError share LookupError

basics

~10 s

TypeError means the object is the wrong kind for the operation; ValueError means the type is right but the particular value is unusable. int([]) raises TypeError, int('twelve') raises ValueError.

solid answer

~40 s

The rule is type versus content. `TypeError` says the operation cannot work on an object of that type at all — `int([])`, `len(5)`, or calling a function with the wrong number of positional arguments. `ValueError` says the type was acceptable and the operation was attempted, but this particular value cannot be used — `int('twelve')`, `math.sqrt(-1)`, unpacking three items into two names. That distinction is a contract: it tells a caller whether to fix the *kind* of thing being passed or the *content*. The same discipline runs through the rest of the built-ins: `KeyError` and `IndexError` both derive from `LookupError`, `ZeroDivisionError` and `OverflowError` from `ArithmeticError`, and `UnicodeDecodeError` from `ValueError` — so a decode failure is caught by `except ValueError`.

code

python · 9 lines
python
def to_count(raw):
    try:
        return int(raw)
    except ValueError:
        return 0
    except TypeError:
        raise

print(to_count("12"), to_count("twelve"))

go deeper

for a junior

Memorise the one-line rule and one example of each: wrong kind of object is TypeError, right kind with unusable content is ValueError. Being able to say which one int('twelve') raises is the concrete check interviewers apply.

for a middle

Explain why the split is worth preserving in your own code: bad content is often recoverable from untrusted input, a bad type is a caller bug. Know the intermediate bases — LookupError over KeyError and IndexError, ValueError over UnicodeDecodeError.

for a senior

Show that you use the names as an API contract. Be ready to discuss why matching on message text is fragile, and how choosing the precise built-in at a module boundary lets callers write narrow handlers instead of blanket ones.

for a principal

Own the error vocabulary of a codebase: which built-ins are raised at public boundaries, where a domain-specific class earns its place instead, and how that contract is documented so consumers can handle failures without reading your implementation.

## The contract carried by a name Python's built-in exception names are not decoration; each one promises something specific about the cause, and a caller writes handlers against that promise. The two most confused are `TypeError` and `ValueError`, and the distinction is genuinely simple once stated: **`TypeError` is about the kind of object, `ValueError` is about its content.** `TypeError` means the operation could not even be attempted on an object of that type. The argument is a list where a number or string was required, a non-callable was called, an unhashable object was used as a dict key, two incompatible types were added, or a function was called with the wrong number of positional arguments or an unexpected keyword. `ValueError` means the type was right, the operation was genuinely attempted, and this particular value cannot be handled. Parsing `'twelve'` as an integer, asking for the square root of a negative number, unpacking a three-element sequence into two names, or passing an out-of-range value to a constructor all raise it. ```python def to_count(raw): try: return int(raw) except ValueError: return 0 except TypeError: raise ``` That shape is the point of the distinction. Bad *content* from an untrusted source is often recoverable — substitute a default, skip the record, ask again. A bad *type* is nearly always a bug in the calling code, so it is left to propagate. Collapsing both into one handler throws away that signal. ## The rest of the family The same discipline runs through the whole built-in tree, and matching by class makes the intermediate nodes useful: * **`LookupError`** is the parent of `KeyError` and `IndexError`. A `KeyError` says a mapping had no such key; an `IndexError` says a sequence index was out of range. Code that walks a nested structure of mixed dicts and lists can catch `LookupError` once and cover both. * **`ArithmeticError`** is the parent of `ZeroDivisionError`, `OverflowError` and `FloatingPointError`. * **`AttributeError`** means the attribute lookup failed on that object; **`NameError`** means no such name was bound in any enclosing scope. `UnboundLocalError` is a `NameError` subclass, raised when a local name is read before assignment. * **`UnicodeDecodeError`** and `UnicodeEncodeError` derive from `UnicodeError`, which derives from `ValueError` — which is exactly right, since the bytes were of the correct type but not decodable under that codec. A handler naming `ValueError` catches them. * **`StopIteration`** is not an error condition at all; it is the protocol signal that an iterator is exhausted. * **`RuntimeError`** is the honest fallback for "something went wrong that no other built-in describes". `RecursionError` derives from it. ## Reading the name as a diagnosis Because the names are contracts, a traceback's final line is already a diagnosis. `KeyError: 'source_lang'` says a mapping was missing a key, and names it. `TypeError: unsupported operand type(s) for +: 'int' and 'str'` says two values of incompatible kinds met — usually a missing conversion at a boundary. `ValueError: invalid literal for int() with base 10: 'twelve'` says the parse was attempted on a real string and failed. Reading the class before reading the message is the fastest triage step there is. ## When you raise one yourself The same promise binds code you write. If a function is handed an argument of the wrong kind and cannot proceed, `TypeError` is correct. If the argument is the right kind but out of range or unparseable, `ValueError` is correct. Reaching for `Exception` or `RuntimeError` when a precise built-in exists strips information from every caller downstream: they can no longer distinguish "my input was malformed" from "my code is wrong", and they end up matching on message text, which is fragile and untranslatable. A useful check when you are unsure: could the caller fix this by converting the argument, or only by supplying different content? Conversion means `TypeError`; different content means `ValueError`. And when neither built-in fits the meaning, that is the signal to reach for a more specific class rather than to misuse one of these two. ## A near-miss worth knowing Indexing a list out of range raises `IndexError`, but *slicing* one out of range raises nothing at all: `items[5:9]` on a three-element list simply returns an empty list. That asymmetry catches candidates who assume any out-of-range access fails loudly, and it illustrates the underlying idea — the exception classes describe what the operation promised, not merely what the numbers looked like. Indexing promises a specific element and cannot deliver; slicing promises a best-effort window and can. `dict.get` is the same idea from the other direction: it promises a default, so it has no `KeyError` to raise.

  • Which exception does Python raise when a function is called with the wrong number of positional arguments?
    `TypeError`. The call itself is malformed — the callable cannot bind the arguments it was given — so it belongs on the type side of the split, not the value side. The same applies to an unexpected keyword argument, to calling a non-callable object, and to a missing required argument. It is a common surprise for candidates who expect `ValueError` there.
  • Why does catching ValueError also catch a decoding failure on bytes?
    `UnicodeDecodeError` derives from `UnicodeError`, which derives from `ValueError`. That placement is deliberate: the bytes were the right type, so the operation was genuinely attempted, and it was the content that could not be decoded under the chosen codec. Matching is by class, so `except ValueError` catches it — useful when parsing untrusted input, and worth knowing so it does not surprise you.
  • What is the difference between AttributeError and NameError?
    `AttributeError` means the object exists but has no such attribute — the lookup `obj.thing` failed. `NameError` means no such name was bound in the local, enclosing, global or builtin scopes at all. `UnboundLocalError`, a `NameError` subclass, is the narrower case where a name is local to the function but read before it was assigned.

saying these in an interview costs you the question

  • Says ValueError and TypeError are interchangeable
  • Expects ValueError when a call has too many arguments
  • Claims int('twelve') raises TypeError
  • Raises bare Exception where a built-in fits exactly
  • Matches on the exception message text instead of the class
  • Thinks KeyError and IndexError share no base class

context