Python Lists vs Arrays: Choosing the Right Sequence for Your Data
Python does not have a single “array” type. When people say “array”, they might mean a Python list, the array.array from the standard library, or a NumPy ndarray. Each has a different purpose and different trade-offs.
Key Differences at a Glance
| Property | list | array.array | numpy.ndarray |
|---|---|---|---|
| Element types | Mixed | Single typed | Single typed |
| Memory layout | Pointers to objects | Raw values | Raw values |
| Random access | O(1) | O(1) | O(1) |
| Numeric computation | Slow | Medium | Fast |
| Built-in | Yes | Yes | No (numpy) |
Python Lists: Flexible by Design
A Python list stores references to objects. Any object can be an element — integers, strings, other lists, None, or custom class instances.
# Lists handle mixed types naturallyrecord = [42, "Alice", True, None, [1, 2, 3]]print(record[1]) # "Alice"record.append({"role": "admin"}) # no restriction on type
# Common list operationsnumbers = [5, 3, 8, 1, 9, 2]numbers.sort() # in-place: [1, 2, 3, 5, 8, 9]subset = numbers[2:5] # slicing: [3, 5, 8]numbers.insert(0, 0) # insert at indexnumbers.remove(8) # remove first occurrenceprint(numbers)Lists are the default Python sequence. Use them unless you have a specific reason to use something else.
array.array: Compact Typed Storage
The array module provides a typed array that stores raw values without per-element object overhead. All elements must share the same type, specified by a type code.
import array
# 'i' = signed int, 'd' = double, 'f' = float, 'B' = unsigned bytereadings = array.array('d', [3.14, 2.71, 1.41, 1.73])print(readings[0]) # 3.14readings.append(0.57) # still mutable
# Will reject wrong typetry: readings.append("text")except TypeError as e: print(f"Rejected: {e}")
# Read/write raw binary — useful for binary file I/Owith open("sensor_data.bin", "wb") as f: readings.tofile(f)
recovered = array.array('d')with open("sensor_data.bin", "rb") as f: recovered.fromfile(f, len(readings))print(list(recovered))Use array.array when you need compact storage for large sequences of numeric values and do not want a NumPy dependency.
NumPy Arrays: Vectorised Computation
NumPy ndarray is the right choice when you do any mathematical work on arrays. Operations are implemented in C and execute without Python interpreter overhead on each element.
import numpy as np
# Create from listarr = np.array([1.0, 2.0, 3.0, 4.0, 5.0])
# Vectorised operations — no loop neededprint(arr * 2) # [2. 4. 6. 8. 10.]print(arr ** 2) # [1. 4. 9. 16. 25.]print(np.sqrt(arr)) # [1. 1.41 1.73 2. 2.23]
# Multi-dimensional arraysmatrix = np.zeros((3, 3))matrix[1, 1] = 1.0 # set centre elementprint(matrix)
# Specify dtype explicitlyuint8_arr = np.array([255, 128, 64], dtype=np.uint8)print(uint8_arr.nbytes) # 3 bytes — 1 byte per elementPractical Decision Guide
# Use list when:config_values = ["localhost", 5432, False] # mixed typesqueue = [] # will grow/shrink dynamicallymatrix_of_lists = [[1, 2], [3, 4], [5, 6]] # nested structure
# Use array.array when:import array# — storing thousands of ints/floats, memory matters, no numpytemperature_log = array.array('f', [36.6, 37.1, 36.8, 37.4])
# Use numpy when:import numpy as np# — any numeric computation, matrix operations, statisticsprices = np.array([100.0, 101.5, 99.8, 103.2])returns = np.diff(prices) / prices[:-1] # percentage changesprint(f"Mean return: {returns.mean():.4f}")Type Codes for array.array
| Code | C type | Bytes | Python type |
|---|---|---|---|
'b' | signed char | 1 | int |
'B' | unsigned char | 1 | int |
'h' | signed short | 2 | int |
'H' | unsigned short | 2 | int |
'i' | signed int | 2+ | int |
'l' | signed long | 4+ | int |
'f' | float | 4 | float |
'd' | double | 8 | float |
Common Mistake: Using list.pop(0) for Queue Operations
This is a performance issue that looks like an array issue:
# SLOW: list.pop(0) is O(n) — shifts all remaining elementsqueue = list(range(10_000))while queue: item = queue.pop(0) # O(n) each time!
# FAST: use collections.deque for FIFO queuesfrom collections import dequequeue = deque(range(10_000))while queue: item = queue.popleft() # O(1)Arrays and lists both have O(1) access by index. The queue use case is about which end you remove from, not about list vs array.