Python Data Types
int, float, complex, bool, str, NoneType - and checking which is which.
What you will be able to do
- Say what a data type is, and why the same operator behaves differently on different types
- Recognise int, float, complex, bool, str, and NoneType
- Check a type with type() and test membership with isinstance()
- Convert between types, and predict which conversions fail
- Explain why 0.1 + 0.2 is not 0.3, and what to use when it matters
- Tell None apart from 0 and from the empty string
- Say at a high level which built-in types can be changed in place
The idea, in plain English
A data type describes what kind of thing an object is, and therefore what you can do with it. That second half is the useful part: `+` joins two strings and adds two numbers, and it is the types of the objects - not the operator - that decide which happens.
Following on from the last lesson: the type belongs to the object, not to the name. `age = 34` does not make `age` an integer variable. It points the name at an integer object, and pointing it at a string tomorrow is perfectly legal.
Six types cover almost everything you will write at this stage. Four are numbers or near enough - int, float, complex, bool - one is text, and one, None, exists to represent the absence of a value. Collections come later.
The single most common bug for beginners lives here: anything arriving from a form, an API, a CSV, or the command line arrives as text. `"100"` looks like a number and is not one, and the moment you try to do arithmetic with it Python will tell you so.
Worked example: Turning "1500" and "3" from a form into a total of 4500.
The same characters, two different things
This is the confusion that costs beginners the most time, and it is worth being blunt about: "100" and 100 are unrelated objects that happen to print the same way. Every operator below behaves differently depending on which one it is given.
What makes it dangerous is that most of these run without raising anything. "100" * 2 is not an error - it is a perfectly good string repetition, and a perfectly wrong total.
+"100" + "1" joins text into "1001". 100 + 1 adds to 101. Mixing them raises TypeError.*"100" * 2 repeats to "100100". 100 * 2 multiplies to 200. No error either way.>"9" > "100" is True - strings compare character by character. 9 > 100 is False.len()len("100") is 3. len(100) raises TypeError - a number has no length.sum()Over strings it fails. Over ints it does what you meant.Tip: Convert at the boundary. The moment data arrives from a form, an API, or a file, turn it into the type you want - then nothing downstream has to wonder.
What int() does to a string
Three outcomes, and only one of them is the one you were picturing. The third is the one that does damage quietly.
Worksint("100") is 100. Clean digits, optional sign, nothing else.Raisesint("12.5"), int("hello"), int("1,000"), int("") all raise ValueError. int() will not guess.Truncatesint(float("12.9")) is 12, not 13. int() cuts towards zero rather than rounding, so int(-12.9) is -12. Use round() when you want rounding.Watch out: If the string came from a user, a file, or an API, it is not yours to trust. Wrap the conversion in try/except and decide what a bad value should do.
Why 0.1 + 0.2 is not 0.3
Run `0.1 + 0.2` and Python prints 0.30000000000000004. Nothing is broken. A float stores a number in binary, and 0.1 has no exact binary representation - in the same way one third has no exact decimal one. The tiny error is in the storage, and arithmetic makes it visible.
This matters the moment money is involved. `0.1 + 0.2 == 0.3` is False, so a total that should match a price will not, and a balance check can fail for a customer who paid exactly the right amount.
For money, use decimal.Decimal and build it from a string - Decimal("0.1"), not Decimal(0.1), because the second one inherits the float error you were trying to escape. For comparing measurements, compare within a tolerance rather than with ==.
Watch out: Never use == between two floats you arrived at by different routes. Use math.isclose(a, b), or work in integer units - store paise or cents rather than rupees or dollars.
None is not zero, and not empty
None is its own type - NoneType - with exactly one value. It means "there is no value here", which is a different statement from "the value is zero" or "the value is an empty string".
The distinction is not academic once you are reading from a database or an API. A middle_name of None means the field was never filled in; a middle_name of "" means someone submitted the form with it blank. A discount of None means no discount applies; a discount of 0 means one applies and it is worth nothing.
Test for it with `is None` rather than `== None`. `is` asks whether it is that exact object, and since there is only ever one None, that is both faster and impossible for a class to override.
Tip: None, 0, and "" are all falsy, so `if not value:` treats them identically. When the difference matters, say `if value is None:` and mean it.
Syntax and examples
count = 34 # int - whole numbers
price = 99.99 # float - decimals
signal = 2 + 3j # complex - real + imaginary
is_active = True # bool - True / False
name = "Chandu" # str - text
middle_name = None # NoneType - no value at all
for value in (count, price, signal, is_active, name, middle_name):
print(value, "->", type(value))a, b = 20, 6
print(a / b) # 3.3333333333333335 true division, always float
print(a // b) # 3 floor division, stays int
print(a % b) # 2 remainder
print(a ** b) # 64000000 power
# Integers have no fixed size - this is a normal int, not a special type
print(999999999999999999999999999999999999 + 1)print(0.1 + 0.2) # 0.30000000000000004
print(0.1 + 0.2 == 0.3) # False
from decimal import Decimal
print(Decimal("0.1") + Decimal("0.2")) # 0.3
print(Decimal("0.1") + Decimal("0.2") == Decimal("0.3")) # True
# Decimal(0.1) would carry the float error in - always build from a string.print(int(True), int(False)) # 1 0
print(True + True) # 2 - legal, and rarely what you meant
print(isinstance(True, int)) # True - bool is a subclass of int
# Which makes this idiom work:
results = [True, False, True, True]
print(sum(results)) # 3 - counts the Truesmissing = None
zero = 0
empty = ""
print(missing == zero) # False
print(missing == empty) # False
print(type(missing)) # <class 'NoneType'>
# The right way to test for it
if missing is None:
print("no value supplied")product_price = "1500" # everything arrives as text
quantity = "3"
total = int(product_price) * int(quantity)
print(total) # 4500
# Untrusted input needs a decision about failure
raw = "not a number"
try:
value = int(raw)
except ValueError:
value = 0 # or re-raise, or ask again
print(value) # 0Tip: A phone number is a string, not an int. Leading zeros survive, +91 is allowed, and you are never going to add two phone numbers together. The same applies to postcodes, PAN numbers, and order references.
The built-in types in this lesson
Six types, and what each one is for.
intWhole numbers, positive or negative, with no size limit beyond memory.
count = 34
floatNumbers with a decimal part, stored in binary - so approximate.
price = 99.99
complexA real and an imaginary part. Common in engineering and signal processing, rare in backend work.
z = 2 + 3j # z.real, z.imag
boolTrue or False. Technically a subclass of int, where True is 1 and False is 0.
is_active = True
strText. Single or double quotes, and it cannot be changed in place once made.
name = "Chandu"
NoneTypeThe type of None, the single object meaning "no value". Test with `is None`.
middle_name = None
Can it be changed in place?
Immutable means the object itself can never change - reassigning a name just points it elsewhere. This is the distinction behind the aliasing surprise from the last lesson, and it decides which types can be dictionary keys.
intImmutable
floatImmutable
complexImmutable
boolImmutable
strImmutable - "abc"[0] = "x" is an error
NoneTypeImmutable
tupleImmutable
listMutable - append, remove, and assignment by index all change the object
dictMutable
setMutable
Try it yourself
The code does not change. Swap the content string and the program does something else entirely.
“print(0.1 + 0.2, 0.1 + 0.2 == 0.3)”
“print("100" * 2, 100 * 2)”
“print(int(12.9), round(12.9))”
“print(True + True, isinstance(True, int))”
What usually goes wrong
The worst part is that it often does not raise. "100" * 2 gives "100100" and "100" + "1" gives "1001" - both run happily and both are wrong. Convert at the edge, where the data arrives.
✗ total = price * quantity # both are str✓ total = int(price) * int(quantity)int() raises ValueError on "12.5", "hello", "1,000", and an empty string. If the value came from a user, a file, or an API, handle the failure rather than hoping.
✗ age = int(request_value)✓ try:
age = int(request_value)
except ValueError:
age = Noneint(12.9) is 12. It truncates towards zero rather than rounding, so int(-12.9) is -12 and not -13. Use round() when you want rounding.
✗ score = int(12.9) # 12✓ score = round(12.9) # 13Two floats that should be equal often are not, because the binary representation carries a tiny error. This bites hardest in money and in test assertions.
✗ if total == 0.3:✓ import math
if math.isclose(total, 0.3):They are all falsy, so `if not value:` cannot tell them apart. When "not supplied" and "supplied as empty" mean different things - and in an API they usually do - check for None explicitly.
✗ if not discount: # true for None AND for 0✓ if discount is None: # only true for NoneBest practices
- Convert incoming data to the type you want at the boundary, then trust it everywhere inside.
- Use isinstance() rather than type(x) == int for checks - it handles subclasses, which is why isinstance(True, int) is True.
- Use decimal.Decimal for money, built from strings, and never float.
- Check for None with `is None`, not `== None`.
- Store identifiers as strings whenever leading zeros, symbols, or formatting matter - phone numbers, postcodes, account numbers.
- Prefer // when you want an integer result, rather than converting after / and hoping.
Practice
Write these yourself before opening anything. Getting them wrong first is most of how this sticks.
Create one variable holding each of 25, 25.5, "25", True, None, and 2 + 3j, then print the type of each.
Show hintHide hint
A tuple and a for loop saves writing six print calls.
Show solutionHide solution
values = (25, 25.5, "25", True, None, 2 + 3j)
for value in values:
print(value, "->", type(value))Convert "100" to an int, "99.99" to a float, 100 to a str, and 1 to a bool. Print each result and its type.
Show solutionHide solution
print(int("100"), type(int("100")))
print(float("99.99"), type(float("99.99")))
print(str(100), type(str(100)))
print(bool(1), type(bool(1)))Build a student record with a name, age, percentage, a pass flag, and remarks that have not been written yet. Print each value with its type.
Show hintHide hint
Remarks that do not exist yet is what None is for.
Show solutionHide solution
name = "Rahul"
age = 22
percentage = 87.5
is_passed = True
remarks = None
for value in (name, age, percentage, is_passed, remarks):
print(value, "->", type(value))A form sends product_price = "1500" and quantity = "3". Work out the total, which should be 4500 as a number.
Show hintHide hint
Convert both before multiplying. If you multiply first you get a very long string.
Show solutionHide solution
product_price = "1500"
quantity = "3"
total = int(product_price) * int(quantity)
print(total) # 4500
print(type(total)) # <class 'int'>A user registration record
Model one registered user with the right type for every field, then print each value alongside its type. The interesting decision is the phone number.
- Store a name, age, email, verification flag, and phone number
- Choose the type for each field deliberately rather than by habit
- Store the phone number as a string, and write a comment saying why
- Include one field that has not been supplied yet, using None
- Print every field with its value and its type
name = ""
age = 0
email = ""
is_verified = False
phone_number = ""
# print each field with its typeShow one solutionHide solution
name = "Rahul Sharma"
age = 28
email = "rahul@example.com"
is_verified = True
# A string, not an int: an int drops the leading zero, cannot hold "+91",
# and there is no arithmetic you would ever want to do with it.
phone_number = "+91 09876 54321"
profile_image = None # not uploaded yet
fields = {
"name": name,
"age": age,
"email": email,
"is_verified": is_verified,
"phone_number": phone_number,
"profile_image": profile_image,
}
for label, value in fields.items():
print(f"{label:14} {str(value):22} {type(value).__name__}")Key points
- A data type says what an object is and what can be done with it - the same operator behaves differently on different types.
- The type belongs to the object, not to the name.
- int, float, complex, bool, str, and NoneType cover almost everything before collections.
- Python integers have no fixed size; floats are binary approximations.
- 0.1 + 0.2 != 0.3. Use decimal.Decimal built from strings for money, and math.isclose for comparisons.
- bool is a subclass of int: True is 1, False is 0, and sum() over booleans counts them.
- Everything from a form, an API, a CSV, or the command line arrives as str.
- int() truncates towards zero rather than rounding, and raises ValueError on anything it cannot read.
- None is its own type meaning "no value" - not 0, not "". Test it with `is None`.
- int, float, bool, str, and tuple cannot be changed in place; list, dict, and set can.
Quick check before you move on
Interview questions
Why is 0.1 + 0.2 not equal to 0.3, and what do you do about it?
Floats follow IEEE 754 binary representation, and 0.1 has no finite binary form - just as a third has no finite decimal one. Each operand carries a small error and arithmetic exposes it. For money use decimal.Decimal constructed from strings, or work in the smallest integer unit such as cents. For general comparison use math.isclose rather than ==.
Is bool a separate type from int?
It is its own type but a subclass of int, so True == 1, True + True == 2, and isinstance(True, int) is True. That inheritance is useful - sum() over a list of booleans counts them - and occasionally surprising, such as when a dict treats the keys 1 and True as the same key.
When would you use type() rather than isinstance()?
Almost never in application code. isinstance() accepts subclasses, which is usually what you want. type(x) is Y is for the rare case where you need the exact type and must exclude subclasses - for example rejecting a bool where a genuine int is required.
What is the difference between None and a missing key?
None is a value meaning "no value". A missing key means the entry does not exist at all. d.get("k") returns None for both, which hides the distinction - "k" in d answers it directly. This matters for APIs, where a field sent as null and a field omitted entirely often have different meanings.
Why does Python not have int32 or int64?
Python integers are arbitrary precision - they grow as needed and are limited only by memory, so overflow simply does not occur. The cost is that they are objects rather than machine words, which makes them slower and larger; libraries like NumPy provide fixed-width types when that cost matters.
What does immutable actually guarantee?
That the object itself can never change after creation. It does not mean the name cannot be rebound - s = s + "x" is fine, it just builds a new string. The guarantee is what makes immutable types safe as dictionary keys and safe to share between threads without locking.
Quiz
- 1.
What are the six fundamental built-in types covered here?
- 2.
What is the difference between "100" and 100?
- 3.
What does int("12.5") do, and what about int(float("12.5"))?
- 4.
Is True equal to 1?
- 5.
Why check `value is None` rather than `value == None`?
- 6.
Which of int, str, list, tuple, dict can be changed in place?
Comments
Sign in to leave a comment. Your name and photo come from Google; nothing else is shared.
Loading comments...
AI
System Design
Backend
- GraphQL8 modules · 69 lessons planned
- Core Python13 modules · 75 lessons planned
- FastAPI5 sections · 20 lessons
- Node.js14 modules · 206 lessons planned
- Node.js Performance7 chapters · 36 topics
- Event Loop Lifecycle6 phases · 3 scenarios
- Docker & Containerization11 modules · 144 lessons planned
- AWS for Developers14 modules · 219 lessons planned
- CI/CD & DevOps Automation10 modules · 134 lessons planned