Python's core data types break down into a few clear families: text (str), numbers (int, float, complex, bool), sequences (list, tuple, range), mappings (dict), sets (set, frozenset), binary data (bytes), and the special None value. Everything in Python is an object, and every value has exactly one type that you can inspect with type(value). The single most useful distinction to learn early is mutable vs. immutable: whether a value can be changed in place after it is created — because that one property governs how assignment, copying, and function arguments behave.
That is the tidy summary an AI Overview will hand you. What it will not show you is which type to reach for in a given situation, the Python-2-era myth that trips up beginners (there is no separate "long" type anymore), or how mutability quietly changes the behavior of your code. The diagram, the decision table, and the worked examples below cover exactly that.
The Python type map at a glance
Strings and characters
Strings are ordered, immutable sequences of Unicode characters. Python has no separate character type the way C does — a single character is just a string of length one. You can write string literals with single quotes, double quotes, or triple quotes for multi-line text; they are equivalent.
name = "Python Programming"
character = "P"
print(name) # Python Programming
print(character) # P
print(len(name)) # 18
print(name.upper()) # PYTHON PROGRAMMING
print(name) # Python Programming <- original is unchanged
Because strings are immutable, methods like .upper() and .replace() return a new string. If you try to assign into a string by index (name[0] = "J") Python raises a TypeError. To build a string efficiently in a loop, gather the parts in a list and call "".join(parts) once at the end.
Numbers
Python has three numeric types plus a boolean type. Note the common myth: Python 3 does not have a separate "long integer" type. That was a Python 2 distinction. In Python 3 the single int type has arbitrary precision, so it grows to hold numbers as large as your memory allows — no overflow, no special syntax.
integer_num = 42
float_num = 3.14
complex_num = 2 + 3j
huge = 2 ** 1000 # a 302-digit int, no overflow
print(type(integer_num)) # <class 'int'>
print(type(float_num)) # <class 'float'>
print(type(complex_num)) # <class 'complex'>
print(True + True) # 2 <- bool is a subclass of int
| Type | Example | Precision / notes | Reach for it when |
|---|---|---|---|
int | 42, -7, 2**1000 | Arbitrary precision, never overflows | Counting, indexing, whole-number math |
float | 3.14, -2.5, 1e9 | 64-bit IEEE 754, ~15–17 significant digits | Measurements, ratios, scientific values |
complex | 2 + 3j | Real + imaginary parts | Signal processing, engineering math |
bool | True, False | Subclass of int (1 and 0) | Flags, conditions, counting truthy items |
Decimal | Decimal("0.1") | Exact base-10 (from the decimal module) | Money and anything where 0.1 + 0.2 != 0.3 matters |
For currency, avoid float — 0.1 + 0.2 famously evaluates to 0.30000000000000004. Use decimal.Decimal (or integer cents) instead.
Collections
Collections store multiple items. The four built-in workhorses differ along three axes: ordered vs. unordered, mutable vs. immutable, and whether duplicates are allowed.
fruits = ["apple", "banana", "cherry"] # list
person = {"name": "John", "age": 30, "city": "NYC"} # dict
coordinates = (10, 20) # tuple
unique_numbers = {1, 2, 3, 4, 5} # set
Which collection should I use?
| Collection | Syntax | Ordered? | Mutable? | Duplicates? | Use it when |
|---|---|---|---|---|---|
| list | [1, 2, 3] | Yes | Yes | Yes | A general-purpose, changeable sequence |
| tuple | (1, 2, 3) | Yes | No | Yes | A fixed record, or a key in a dict/set |
| dict | {"k": "v"} | Yes (3.7+) | Yes | Keys unique | Lookups by name/key; structured records |
| set | {1, 2, 3} | No | Yes | No | Deduplication and fast membership tests |
A quick rule of thumb: if you need to look things up by name, use a dict; if you need fast "is this in here?" checks or to remove duplicates, use a set; if order and mutation matter, use a list; if the data should never change, use a tuple. Dicts and sets rely on hashing, which is why their keys/members must themselves be immutable (you can use a tuple as a dict key, but not a list).
Date and time
Dates and times get their own types from the datetime module. You can store a date as a string, but a real datetime object gives you arithmetic, comparison, formatting, and timezone handling for free.
from datetime import datetime, timedelta
now = datetime.now()
print(now) # 2026-07-18 14:05:32.115003
tomorrow = now + timedelta(days=1) # date arithmetic
print(tomorrow)
formatted = now.strftime("%Y-%m-%d %H:%M:%S") # format to string
print(formatted) # 2026-07-18 14:05:32
parsed = datetime.strptime("2026-07-18", "%Y-%m-%d") # string back to datetime
print(parsed.year) # 2026
The advantage is concrete: with real datetime objects you can subtract two dates to get a timedelta, sort a list of events correctly, and convert to and from strings deliberately with strftime/strptime. Two dates stored as plain strings cannot be subtracted or reliably compared without converting them first.
Inspecting and converting types
Every value carries its type. Use type() to see it and isinstance() to test it — isinstance is preferred in real code because it also matches subclasses.
value = 42
print(type(value)) # <class 'int'>
print(isinstance(value, int)) # True
# Explicit conversion ("casting")
print(int("100")) # 100 (str -> int)
print(str(3.14)) # '3.14' (float -> str)
print(float("2.5")) # 2.5 (str -> float)
print(list("abc")) # ['a', 'b', 'c']
print(set([1, 1, 2])) # {1, 2} (dedupe)
Python is dynamically typed (a variable can be rebound to any type) but strongly typed (it will not silently add a string to an integer — "3" + 5 raises a TypeError). Knowing which built-in type you are holding, and whether it is mutable, is most of what you need to write correct, predictable Python.