What Are Data Types?
A data type tells Python what kind of value a variable holds and what operations are legal on it. You can add two integers, but you can't divide a string by a number. Python's type system enforces these rules and raises clear errors when you mix incompatible types.
Python is dynamically typed, meaning you don't declare types when creating variables β Python infers the type from the value. However, types still exist and matter at runtime.
# Python infers the type automatically
x = 42 # int
y = 3.14 # float
name = "Alice" # str
active = True # bool
nothing = None # NoneType
# Check each type
print(type(x)) # <class 'int'>
print(type(y)) # <class 'float'>
print(type(name)) # <class 'str'>
print(type(active)) # <class 'bool'>
print(type(nothing)) # <class 'NoneType'>
Integers (int)
Integers are whole numbers β positive, negative, or zero β with no decimal point. Python's int type has unlimited precision: there is no maximum value (unlike C/Java where integers overflow). You can compute with numbers as large as your RAM allows.
# Regular integers
age = 25
temperature = -10
score = 0
# Large integers β no overflow in Python!
big_number = 1_000_000_000_000 # underscores for readability
factorial_20 = 2432902008176640000
print(factorial_20)
# Different integer literals
decimal = 255 # base 10 (normal)
hexadecimal = 0xFF # base 16 β 255
octal = 0o377 # base 8 β 255
binary = 0b11111111 # base 2 β 255
print(decimal, hexadecimal, octal, binary) # all print as 255
# Integer arithmetic
print(10 + 3) # 13
print(10 - 3) # 7
print(10 * 3) # 30
print(10 ** 3) # 1000 (exponentiation)
print(10 // 3) # 3 (floor division β integer result)
print(10 % 3) # 1 (modulo β remainder)
Python allows underscores as visual separators in numeric literals: 1_000_000 is the same as 1000000 but much easier to read. This works for integers, floats, and hex values.
Floats (float)
Floats represent real numbers with a decimal point. Python floats are 64-bit double-precision IEEE 754 values, giving about 15β17 significant digits of precision. This high precision is sufficient for most real-world calculations.
pi = 3.14159265358979
height = 1.75
temperature = -273.15
# Scientific notation
speed_of_light = 3e8 # 3 Γ 10^8 = 300000000.0
tiny = 1.5e-10 # 1.5 Γ 10^-10
print(speed_of_light) # 300000000.0
print(tiny) # 1.5e-10
# Float arithmetic
print(0.1 + 0.2) # 0.30000000000000004 (floating point precision!)
print(round(0.1 + 0.2, 2)) # 0.3 β use round() for display
# Division always returns float in Python 3
print(7 / 2) # 3.5
print(7 // 2) # 3 (floor division β int)
# Special float values
import math
print(math.inf) # inf
print(-math.inf) # -inf
print(math.nan) # nan (Not a Number)
0.1 + 0.2 does not equal exactly 0.3 in most programming languages. This is a fundamental property of binary floating-point representation, not a Python bug. For financial calculations requiring exact decimal arithmetic, use Python's decimal module: from decimal import Decimal.
Strings (str)
Strings are sequences of characters used to represent text. In Python, strings are immutable β you can't change a string in place; any "modification" creates a new string.
# Creating strings
greeting = "Hello, World!"
single = 'Also a valid string'
multiline = """This spans
multiple lines"""
# String operations
name = "Python"
print(len(name)) # 6 β length
print(name[0]) # P β indexing
print(name[-1]) # n β negative indexing (from end)
print(name[0:3]) # Pyt β slicing
print(name.upper()) # PYTHON
print(name.lower()) # python
print(name * 3) # PythonPythonPython β repetition
# String is immutable
word = "hello"
# word[0] = "H" # TypeError! Can't change in place
word = "H" + word[1:] # Must create a new string
print(word) # Hello
Booleans (bool)
Booleans represent truth values: True or False. They are used extensively in conditional statements and comparisons. In Python, bool is actually a subclass of int β True equals 1 and False equals 0, which leads to some interesting behavior.
is_active = True
is_deleted = False
print(type(is_active)) # <class 'bool'>
# bool is a subclass of int
print(True + True) # 2
print(True + 1) # 2
print(False + 10) # 10
print(True * 5) # 5
# Comparison operators return booleans
print(10 > 5) # True
print(10 == 5) # False
print(10 != 5) # True
# "Truthy" and "Falsy" values
# These are considered False:
print(bool(0)) # False
print(bool(0.0)) # False
print(bool("")) # False (empty string)
print(bool([])) # False (empty list)
print(bool(None)) # False
# These are considered True:
print(bool(1)) # True
print(bool(-42)) # True
print(bool("hello")) # True
print(bool([1, 2])) # True
In Python, booleans are True and False with capital first letters. Writing true or false (lowercase) causes a NameError. This trips up developers coming from JavaScript or other languages.
NoneType (None)
None is Python's way of representing "no value" or "absence of a value". It is its own type (NoneType) and there is only one None object in Python β it's a singleton. This is conceptually similar to null in Java/JavaScript or nil in other languages.
result = None
print(result) # None
print(type(result)) # <class 'NoneType'>
# Functions that don't return a value return None implicitly
def do_nothing():
pass
value = do_nothing()
print(value) # None
# The correct way to check for None is using 'is'
if result is None:
print("No result yet")
# Avoid using == for None comparison
# result == None β works but not Pythonic
# result is None β correct, recommended
# None is falsy
if not result:
print("result is falsy (None counts)")
# None as a default/placeholder
user_name = None
user_email = None
# (later in program, user fills these in)
While x == None often works, the correct, Pythonic way is x is None. The is operator checks identity (same object in memory), while == checks equality (which can be overloaded by custom classes in surprising ways).
Complex Numbers (complex)
Python has built-in support for complex numbers using the form a + bj, where a is the real part and b is the imaginary part. This is particularly useful in scientific computing, signal processing, and engineering applications.
# Creating complex numbers
c1 = 3 + 4j
c2 = complex(2, -1) # 2 - 1j
print(c1) # (3+4j)
print(c2) # (2-1j)
print(type(c1)) # <class 'complex'>
# Accessing parts
print(c1.real) # 3.0
print(c1.imag) # 4.0
print(c1.conjugate()) # (3-4j)
# Complex arithmetic
print(c1 + c2) # (5+3j)
print(c1 * c2) # (10+5j)
# Magnitude (absolute value)
import math
magnitude = math.sqrt(c1.real**2 + c1.imag**2)
print(magnitude) # 5.0
print(abs(c1)) # 5.0 β abs() works on complex numbers
Type Checking: type() and isinstance()
Python provides two built-in tools to inspect types at runtime. Knowing when to use each is important.
# type() β returns the exact type object
x = 42
print(type(x)) # <class 'int'>
print(type(x) == int) # True β exact type match
# isinstance() β checks if object is an instance of a type or subclass
print(isinstance(x, int)) # True
print(isinstance(x, float)) # False
print(isinstance(x, (int, float))) # True β check multiple types
# Why isinstance() is often better: it handles inheritance
# bool is a subclass of int, so:
flag = True
print(type(flag) == int) # False β exact match fails
print(isinstance(flag, int)) # True β recognizes inheritance!
print(isinstance(flag, bool)) # True
# Practical usage: validating input type
def process_number(n):
if not isinstance(n, (int, float)):
raise TypeError(f"Expected int or float, got {type(n).__name__}")
return n * 2
print(process_number(5)) # 10
print(process_number(3.14)) # 6.28
# process_number("5") # TypeError!
Mutable vs Immutable Types
This is one of the most important concepts in Python. Immutable objects cannot be changed after creation. Mutable objects can be modified in place. All primitive types (int, float, str, bool, complex, NoneType) are immutable.
| Category | Type | Mutable? | Example |
|---|---|---|---|
| Primitive | int | No (immutable) | 42 |
| Primitive | float | No (immutable) | 3.14 |
| Primitive | str | No (immutable) | "hello" |
| Primitive | bool | No (immutable) | True |
| Primitive | NoneType | No (immutable) | None |
| Collection | list | Yes (mutable) | [1, 2, 3] |
| Collection | dict | Yes (mutable) | {"a": 1} |
| Collection | set | Yes (mutable) | {1, 2, 3} |
| Collection | tuple | No (immutable) | (1, 2, 3) |
# Immutability of strings β "changes" create new objects
original = "hello"
print(id(original)) # memory address, e.g. 140234567890
modified = original.upper()
print(id(modified)) # DIFFERENT address β new object
print(original) # "hello" β unchanged
# Integer caching (Python caches small integers)
a = 42
b = 42
print(a is b) # True β same object (cached)
a = 1000
b = 1000
print(a is b) # May be False β large ints not cached
# Mutable types: changes affect the original
my_list = [1, 2, 3]
alias = my_list # both point to same list
alias.append(4)
print(my_list) # [1, 2, 3, 4] β changed via alias!
# To avoid this, make a copy:
original_list = [1, 2, 3]
copy_list = original_list.copy()
copy_list.append(4)
print(original_list) # [1, 2, 3] β unaffected
Quick Reference: All Primitive Types
| Type | Keyword | Example Literal | Truthy when | Falsy when |
|---|---|---|---|---|
| Integer | int | 42, -7, 0 | Non-zero | 0 |
| Float | float | 3.14, -0.5 | Non-zero | 0.0 |
| String | str | "hello", '' | Non-empty | "" |
| Boolean | bool | True, False | True | False |
| None | NoneType | None | Never | Always |
| Complex | complex | 3+4j | Non-zero | 0j |
ποΈ Practical Exercise
Create a Python script that demonstrates all six primitive types:
- Create one variable of each type:
int,float,str,bool,NoneType,complex. - Print each variable's value and its type using
type(). - Use
isinstance()to check if your integer variable is also an instance ofbool(it's not). Then check if yourTruevariable is an instance ofint(it is!). - Demonstrate that strings are immutable by trying
.upper()and showing the original is unchanged.
π₯ Challenge Exercise
Write a function called describe_value(x) that accepts any value and prints a full report: the value itself, its type name (just the name, not the full class string), whether it is truthy or falsy, and whether it is mutable or immutable. Test it with at least one value of each primitive type plus a list and a tuple. Use isinstance() for type checking rather than type().
Interview Questions on Data Types
- What is the difference between
type()andisinstance()? When should you use each? - Why is
0.1 + 0.2 != 0.3in Python? How do you work around this? - What is the difference between mutable and immutable types? Give examples of each.
- Why is
boola subclass ofintin Python? What are the implications? - What is
NoneTypeand how should you check if a variable isNone? - Python integers have unlimited precision. Why doesn't this make them infinitely fast?
- What are "truthy" and "falsy" values? List all the falsy built-in values in Python.
- What is integer caching in Python and why does
a is breturnTruefor small integers?
π Summary
- Python has six primitive types:
int,float,str,bool,NoneType, andcomplex. intholds whole numbers with unlimited precision; use//for integer division,%for remainder.floatholds decimal numbers (64-bit); be aware of floating-point precision issues with0.1 + 0.2.- All primitive types are immutable β they cannot be changed in place; operations return new objects.
boolis a subclass ofint:True == 1andFalse == 0.- Use
x is None(notx == None) to check forNone. - Use
type()for exact type checks; useisinstance()for inheritance-aware checks. - Zero, empty strings, empty containers, and
Noneare all falsy; everything else is truthy.
Related Topics
Frequently Asked Questions
Python is strongly typed but dynamically typed. Strongly typed means you can't implicitly mix incompatible types (e.g., "5" + 5 raises a TypeError). Dynamically typed means types are checked at runtime, not compile time, and variables don't need explicit type declarations.
In Python 3, / always performs true division and returns a float: 7 / 2 = 3.5. The // operator performs floor division and returns an int (when both operands are integers): 7 // 2 = 3. This was a major change from Python 2, where / between two integers performed integer division.
Yes. Python variables are just names that point to objects. You can reassign a variable to a value of any type at any time: x = 5 then x = "five". This is called dynamic typing. The variable itself doesn't have a type β the object it points to has a type.
isinstance() respects inheritance, while type() requires an exact match. For example, isinstance(True, int) returns True (correct β bool is a subclass of int), but type(True) == int returns False. In general code, you should use isinstance() unless you specifically need to exclude subclasses.
They are all falsy but represent different concepts. None means "no value / absence". 0 means the number zero. False means a negative truth value. "" means an empty text string. They are not equal to each other: None == 0 is False, None == False is False. Use the right one for its semantic meaning.