AlgoMaster Logo

Type Conversion

Medium Priority23 min readUpdated June 6, 2026
Listen to this chapter
Unlock Audio

Real programs constantly move data between types. A price comes in from a form as a string, and you need a number to do math on it. A list of products needs to become a set to remove duplicates. A boolean check decides whether to show a message. This lesson covers the conversion functions Python gives you, when Python converts automatically, and what happens when a conversion can't succeed.

Two Kinds of Conversion

Python has two ways to move a value from one type to another:

  • Implicit conversion happens automatically when Python knows the conversion is safe. The classic case: mixing an int and a float in an expression makes the result a float.
  • Explicit conversion is when you ask for it with a function like int(), float(), str(), bool(), list(), tuple(), set(), or dict().

The distinction matters because implicit conversion is rare in Python. The language prefers to make you ask. That's why 1 + "2" raises an error in Python where it might "just work" in JavaScript or PHP.

Here Python implicitly promoted quantity (an int) to a float so it could multiply with price. The result is a float. We didn't write float(quantity) anywhere; the interpreter did it for us because mixing int and float in arithmetic is safe and unambiguous.

Now compare that to mixing a string and a number:

What's wrong with this code?

price is a string, not a number. "19.99" * 3 is valid Python, but it means "repeat the string three times", not "multiply 19.99 by 3". The fix is explicit conversion:

This is the rule of thumb: when the types involved are different in a way that could mean two things, Python won't guess. You convert.

Converting to int

int(x) turns x into a whole number. It accepts numbers, strings that look like integers, and booleans.

A few things to notice:

  • Truncation, not rounding. int(3.7) is 3, not 4. int(-3.7) is -3, not -4. Python drops the fractional part and moves toward zero. For rounding, use round() instead.
  • Leading and trailing whitespace is fine. int(" 42 ") works.
  • Booleans convert cleanly. True is 1, False is 0. This makes sense because bool is technically a subclass of int in Python.

The rounding distinction matters more than people expect:

int() always moves toward zero. round() follows standard rounding rules.

Strings have to look like valid integers. Anything else raises ValueError:

Even though 3.7 would convert fine if it were a number, int() won't parse a string that contains a decimal point. To go from a decimal string to an integer, do it in two steps: int(float("3.7")) gives 3.

Parsing with a different base

int() takes a second argument: the base of the number in the string. This is useful for hex (16), octal (8), or binary (2) inputs.

int("12", 16) reads "12" as a base-16 (hex) number, which equals 18 in decimal. The 0x, 0o, and 0b prefixes are optional; int() recognizes them.

The base argument only works with string input. int(12, 16) raises TypeError because 12 is already a number, so there's no string to parse.

Converting to float

float(x) turns x into a decimal number. It accepts numbers, strings, and booleans.

Notes:

  • An int converts cleanly. 42 becomes 42.0.
  • A string like "19.99" parses as you'd expect.
  • Scientific notation works: "1e3" is 1 x 10^3, which is 1000.0.
  • Whitespace around the number is allowed.
  • True and False become 1.0 and 0.0.

Two special string values are also legal:

inf is positive infinity, -inf is negative infinity, and nan is "not a number". You won't use these often, but they're worth knowing exist.

Like int(), bad strings raise ValueError:

A common e-commerce use is parsing a price string from a form or a CSV file:

Without float(), form_input * quantity would have repeated the string three times, giving "29.9929.9929.99".

Converting to str

str(x) turns anything into a string. There is no case where str() raises an error on a built-in type. Every Python object has a string representation, even None.

The most common use is building messages that mix text and numbers. You can use str() directly, but f-strings are almost always cleaner:

The f-string version doesn't call str() explicitly. It does it internally. F-strings handle the conversion for you and read more naturally.

Converting to bool

bool(x) follows Python's falsiness rules. Anything in the "falsy" set becomes False. Everything else becomes True.

The falsy values are:

TypeFalsy values
Numbers0, 0.0, 0j
Strings""
Lists[]
Tuples()
Setsset()
Dicts{}
SpecialNone, False

Everything else is truthy:

Two of these surprise beginners:

  • bool(" ") is True because the string contains a space character, which is not the same as an empty string.
  • bool([0]) is True because the list has one element, even though that element happens to be 0. The truthiness check is "is this collection empty?", not "are its contents falsy?".

You rarely call bool() directly. Python applies it automatically when you write if x: or while x: or any boolean context. Knowing the rules helps debug "why did my if-statement go the wrong way?" moments.

if cart: is equivalent to if bool(cart):, which is if False: because the list is empty. This idiom (if some_collection:) is the Pythonic way to check whether a collection has anything in it. Don't write if len(cart) > 0: unless you need the count anyway.

Converting to list, tuple, and set

These three functions all accept an iterable: anything you can loop over. That includes lists, tuples, sets, strings, dictionary keys, generators, and more. The output is a new collection of the requested type.

The main reasons to convert between these three:

  • `list(x)` when you want a mutable, ordered sequence you can index, append to, and modify.
  • `tuple(x)` when you want an immutable, ordered sequence (useful for dictionary keys or as a "frozen" version of a list).
  • `set(x)` when you want unique elements, fast membership checks, or set operations like union and intersection.

A common pattern: deduplicate a list of products while preserving order.

set() deduplicates, but it doesn't preserve order in the way most people expect. If you need both deduplication and original order, use dict.fromkeys():

This works because regular dictionaries preserve insertion order (since Python 3.7), and dict.fromkeys() builds a dictionary using the iterable's values as keys (which dedupes).

Converting in the other direction is just as easy:

You can't append to a tuple directly because tuples are immutable. Converting to a list, modifying, and (if needed) converting back is the standard workaround.

A diagram of how these conversions relate:

The diagram shows that all three constructors accept the same kind of input (any iterable) but produce containers with different properties. Pick the one whose properties match what you actually need.

Converting to dict

dict() builds a dictionary. It accepts several different shapes of input, which trip up beginners.

The two most common ways:

This keyword-argument form is the most readable when you know the keys at write time. It only works when the keys are valid Python identifiers (no spaces, doesn't start with a digit).

The other common form takes an iterable of key-value pairs:

Each element of the iterable must be a two-element sequence. The first becomes the key, the second becomes the value. This form is useful when the pairs come from another data source (a CSV reader, a zip() of two lists, an API response).

A real-world pattern: converting two parallel lists into a dictionary.

zip() pairs up the two lists into tuples like ("Wireless Mouse", 19.99). dict() then turns those pairs into key-value entries.

You can also copy an existing dict:

dict(default_settings) creates a shallow copy, so modifying user_settings doesn't affect the original.

When Conversion Fails

You've seen ValueError from int("abc") already. Here's the broader picture: each conversion function has its own failure modes.

The traceback's last line is the part that matters. ValueError means the function recognized the input type (it's a string), but the value isn't something it knows how to convert.

There's also TypeError, which happens when the input isn't a type the function accepts at all:

The message depends on the function and how it's called, but the type is the giveaway. TypeError means "wrong shape of input", ValueError means "right shape, wrong value".

A summary table:

FunctionCommon errorTypical cause
int(x)ValueErrorNon-numeric string like "abc" or "3.7"
float(x)ValueErrorNon-numeric string like "abc"
str(x)(never raises on built-ins)N/A
bool(x)(never raises on built-ins)N/A
list(x)TypeErrorx isn't iterable, like list(42)
tuple(x)TypeErrorx isn't iterable
set(x)TypeErrorx isn't iterable, or contains unhashable elements
dict(x)ValueError / TypeErrorx isn't pairs of key-value

You'll handle these failures gracefully using try / except. The shape looks like this:

try runs the risky code. If it raises ValueError, control jumps to the except block. If everything succeeds, the except block is skipped. For now, recognize the pattern and know it exists.

A diagram of the conversion lifecycle:

The check happens in two stages. First, is the input even something this function can work with? If you pass a list to int(), you'll hit TypeError. Second, given that the type is OK, can the actual value be converted? If you pass "abc" to int(), the string is the right type but the value isn't a number, so you get ValueError.

Implicit Promotion in Arithmetic

Implicit conversion in Python is narrow. It happens in arithmetic between numeric types, and that's mostly it.

Rules:

  • int mixed with int stays int.
  • int mixed with float promotes to float.
  • bool mixed with int or float promotes to int or float (since True is 1 and False is 0).

Notice that True + 1 is 2, not True. As soon as you do arithmetic on a bool, you're working with integers. This is occasionally useful: sum([True, False, True, True]) gives you the count of True values, which is 3.

What Python won't do implicitly: convert strings to numbers, or numbers to strings, in arithmetic. Those have to be explicit.

The fix is "Price: $" + str(19.99) or, much better, an f-string: f"Price: ${19.99}".

Division has one more quirk worth knowing. The / operator always produces a float, even when both operands are integers and the result is whole. The // operator does integer division (truncating toward negative infinity).

If you want an integer back from /, you have to convert explicitly: int(10 / 2). Or just use //.

Quiz

Type Conversion Quiz

10 quizzes