Back to Blog
Python

Python Built-in Data Types: A Practical Reference

python built in data types: Practical guide to Python's built-in data types: behavior, performance characteristics, and when to choose each type for real-world code.

pythondata-typesmutable-vs-immutablepython-collectionspython-performance
A clean diagram showing Python's built-in data types grouped into scalar, sequence, mapping, set, and binary categories.

python built in data types requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.

Python's built-in data types—int, float, str, bool, list, tuple, dict, set, frozenset, bytes, and bytearray—are the primitives behind nearly every Python program. Each type has distinct mutability, memory, and performance characteristics that shape how your code behaves under real workloads. Selecting the wrong type for a collection that grows to thousands of elements can turn an O(1) operation into an O(n) scan, or force avoidable allocations in a hot loop.

The Core Scalar Types: int, float, str, and bool

int in Python is arbitrary precision. It does not overflow at 32 or 64 bits; it grows as needed. This is convenient for calculations that would overflow in languages like C or Java, but it means large integers consume more memory and arithmetic becomes slower as the value grows. For most application code this is irrelevant, but if you are processing millions of large numbers, the cost is real.

float is a double-precision IEEE 754 value. It has the same precision limitations as any double: 0.1 + 0.2 does not equal 0.3 exactly. When you need exact decimal arithmetic, use the decimal module, not float.

# float precision behavior print(0.1 + 0.2) # 0.30000000000000004

str is immutable and stores Unicode code points. Every string operation that appears to modify a string actually creates a new object. Concatenating many strings in a loop is O(n²) because each + creates a new string and copies the previous content. Use ''.join(...) for bulk concatenation.

bool is a subclass of int. True is 1 and False is 0. This means True + True == 2 works, which is occasionally useful but more often a source of subtle bugs when booleans are used in arithmetic contexts.

List vs Tuple: Sequence Types with Different Contracts

list is mutable and resizable. It is implemented as a dynamic array with overallocation, so appending is amortized O(1). Inserting or removing from the middle is O(n) because elements must shift.

tuple is immutable and fixed-size. It uses less memory than a list because it does not need the overallocation buffer or the resize logic. Tuples are also hashable when all their elements are hashable, which makes them usable as dictionary keys or set members.

# tuple as dictionary key coordinates = {(10, 20): "origin", (30, 40): "target"}

Choose a tuple when the sequence length is fixed and the elements represent a single logical unit. Choose a list when the collection grows, shrinks, or is reordered.

dict and set: Hash-Based Types

dict is a hash table mapping keys to values. Since Python 3.6, dicts preserve insertion order. Lookup, insertion, and deletion are average O(1), but worst-case O(n) when many keys collide.

Keys must be hashable. Mutable types like list, dict, and set are not hashable and cannot be used as keys. If you need a list-like key, convert it to a tuple first.

set is a hash table of keys with no associated values. It supports O(1) membership testing, union, intersection, and difference. frozenset is the immutable version and is itself hashable, so it can be used as a dict key or set element.

# set membership and operations active_ids = {101, 102, 103} blocked_ids = {103, 104} available = active_ids - blocked_ids # {101, 102}

Memory for dict and set is larger per element than list because the hash table needs extra slots to keep the load factor low. If you only need ordered iteration over a small collection, a list may be more memory-efficient.

bytes and bytearray: Binary Data Types

bytes is an immutable sequence of integers in the range 0–255. bytearray is its mutable counterpart. Both are used for binary protocols, file I/O, and network data.

bytes objects are hashable and can be used as dict keys. bytearray cannot be hashed because it is mutable.

# reading binary data with open("image.bin", "rb") as f: data = f.read() # bytes

When you need to modify binary data in place, bytearray avoids creating a new object for every change, which matters when processing large buffers.

Performance and Memory Characteristics

The table below summarizes the practical performance characteristics of the main built-in types.

TypeMutabilityLookup/IndexInsert/AppendMemory Profile
intImmutableN/AN/AVariable, grows with value
floatImmutableN/AN/AFixed 8 bytes
strImmutableO(1) indexN/A1 byte per ASCII char, more for Unicode
listMutableO(1) indexAmortized O(1) appendOverallocated buffer
tupleImmutableO(1) indexN/AMinimal, no overallocation
dictMutableO(1) averageO(1) averageHash table with spare slots
setMutableO(1) averageO(1) averageHash table with spare slots
bytesImmutableO(1) indexN/ACompact binary storage
bytearrayMutableO(1) indexAmortized O(1) appendResizable buffer

The practical takeaway: for large collections, measure the memory footprint with sys.getsizeof and the time with timeit rather than guessing. The asymptotic complexity is usually the deciding factor, but constant factors matter when you process millions of elements.

Choosing the Right Type for the Job

The decision depends on three questions: is the collection mutable, is order significant, and is membership testing or keyed access the dominant operation?

  • Use a tuple when the sequence is fixed and represents a single record, such as coordinates or a version number.
  • Use a list when you append, remove, or reorder elements.
  • Use a dict when you need to map keys to values and the key set is known at access time.
  • Use a set when you only need membership testing and duplicate elimination, and order is irrelevant.
  • Use frozenset when you need a set that must be hashable, such as a key in another dict.

For example, tracking unique user IDs in a session is a set operation, not a list operation:

# deduplication with set seen = set() for event in event_stream: if event.user_id not in seen: seen.add(event.user_id) process(event)

Common Pitfalls and Edge Cases

Mutable default arguments are the classic Python trap. A default value is evaluated once at function definition time, so a mutable default like [] is shared across all calls.

def add_item(item, cache=[]): cache.append(item) return cache

The fix is to use None as the default and create a fresh list inside the function.

Hashability is another edge case. A tuple containing a list is not hashable, even though the tuple itself is immutable. The hash is computed from the elements, and a mutable element breaks the contract.

When comparing types, remember that == checks value equality, while is checks identity. For small integers and short strings, Python may intern objects, so is can return True for equal values. Relying on this behavior is fragile and should be avoided in production code.

When Type Selection Affects Production Behavior

In a long-running service, the choice between list and set for membership testing is the difference between O(n) and O(1) per check. If you check membership inside a hot loop over thousands of items, the list version degrades quadratically while the set version stays linear.

Similarly, storing large binary payloads as bytearray instead of repeatedly concatenating bytes objects avoids repeated allocation and copying. For network packet processing or file chunking, this is a measurable difference.

The immutable types (tuple, frozenset, bytes) are safe to share across threads without locks because they cannot be mutated. If your service uses a shared configuration or cache keyed by a tuple, you get thread safety for free. Mutable types require explicit synchronization.

python built in data types: Practical Usage and Code Example | RYUSLOG DEV