Skip to main content

Python NumPy: How to Find the Memory Size of a NumPy Array in Python

Understanding memory consumption is essential when working with large datasets in scientific computing and machine learning. NumPy arrays are designed for memory efficiency, storing data in contiguous blocks rather than as scattered Python objects. Knowing exactly how much RAM your arrays consume helps you optimize data pipelines, prevent out-of-memory errors, and choose appropriate data types for your hardware constraints.

The .nbytes attribute provides the most direct way to get total memory used by array elements:

import numpy as np

# Create arrays of different sizes
small_array = np.array([1.0, 2.0, 3.0, 4.0, 5.0])
large_array = np.zeros((1000, 1000))

print(f"Small array: {small_array.nbytes} bytes")
print(f"Large array: {large_array.nbytes:,} bytes")
print(f"Large array: {large_array.nbytes / (1024**2):.2f} MB")

Output:

Small array: 40 bytes
Large array: 8,000,000 bytes
Large array: 7.63 MB
What nbytes Measures

The .nbytes attribute returns the total bytes consumed by the array's data buffer only. It doesn't include Python object overhead (typically 96-112 bytes) or metadata. For large arrays, this overhead is negligible.

Understanding Array Memory Components

NumPy provides attributes to understand how memory is calculated:

import numpy as np

# Create a 2D array
matrix = np.array([[1, 2, 3], [4, 5, 6]], dtype=np.int32)

print(f"Shape: {matrix.shape}") # Shape: (2, 3)
print(f"Total elements: {matrix.size}") # Total elements: 6
print(f"Bytes per element: {matrix.itemsize}") # Bytes per element: 4
print(f"Data type: {matrix.dtype}") # Data type: int32
print(f"Total bytes: {matrix.nbytes}") # Total bytes: 24

# Verify: size × itemsize = nbytes
assert matrix.size * matrix.itemsize == matrix.nbytes

The relationship is straightforward:

  • size: Total number of elements
  • itemsize: Bytes per element
  • nbytes: Total bytes = size × itemsize

Impact of Data Types on Memory

Choosing the right dtype dramatically affects memory consumption:

import numpy as np

# Same data, different types
n_elements = 1_000_000

arrays = {
'int8': np.zeros(n_elements, dtype=np.int8),
'int32': np.zeros(n_elements, dtype=np.int32),
'int64': np.zeros(n_elements, dtype=np.int64),
'float32': np.zeros(n_elements, dtype=np.float32),
'float64': np.zeros(n_elements, dtype=np.float64),
'complex128': np.zeros(n_elements, dtype=np.complex128),
}

print("Memory usage for 1 million elements:")
for dtype_name, arr in arrays.items():
mb = arr.nbytes / (1024 ** 2)
print(f" {dtype_name:12}: {mb:6.2f} MB ({arr.itemsize} bytes/element)")

Output:

Memory usage for 1 million elements:
int8 : 0.95 MB (1 bytes/element)
int32 : 3.81 MB (4 bytes/element)
int64 : 7.63 MB (8 bytes/element)
float32 : 3.81 MB (4 bytes/element)
float64 : 7.63 MB (8 bytes/element)
complex128 : 15.26 MB (16 bytes/element)
Memory Optimization

If your values fit within smaller ranges, use smaller dtypes:

  • Ages (0-150): uint8 instead of int64 = 8× savings
  • Probabilities (0-1): float32 instead of float64 = 2× savings
  • Boolean flags: bool (1 byte) instead of default integer

Data Type Reference

dtypeBytesValue RangeUse Case
bool1True/FalseFlags, masks
int81-128 to 127Small integers
uint810 to 255Image pixels, ages
int324±2.1 billionGeneral integers
int648±9.2 quintillionLarge counts
float324±3.4×10³⁸ML models, graphics
float648±1.8×10³⁰⁸Scientific computing
complex12816Complex numbersSignal processing

NumPy vs Python List Memory

NumPy arrays are far more memory-efficient than Python lists for numeric data:

import numpy as np
import sys

n = 10000

# Python list of integers
py_list = list(range(n))

# NumPy array
np_array = np.arange(n, dtype=np.int64)

# Python list: stores pointers + integer objects
list_size = sys.getsizeof(py_list) # Just the list structure
# Each integer object adds ~28 bytes

# NumPy: contiguous memory block
array_size = np_array.nbytes

print(f"NumPy array data: {array_size:,} bytes")
print(f"Python list structure: {list_size:,} bytes")
print(f"Python list estimate (with objects): ~{n * 28 + list_size:,} bytes")

Output:

NumPy array data: 80,000 bytes
Python list structure: 80,056 bytes
Python list estimate (with objects): ~360,056 bytes
getsizeof Limitations

sys.getsizeof() on a Python list returns only the list structure size (pointers), not the objects it references. For NumPy arrays, getsizeof() includes object overhead but .nbytes gives the actual data size.

Memory Profiling Utility

Create a helper function for detailed memory analysis:

import numpy as np

def memory_report(arr, name="Array"):
"""Generate detailed memory report for a NumPy array."""
bytes_total = arr.nbytes

# Convert to appropriate unit
if bytes_total >= 1024**3:
size_str = f"{bytes_total / 1024**3:.2f} GB"
elif bytes_total >= 1024**2:
size_str = f"{bytes_total / 1024**2:.2f} MB"
elif bytes_total >= 1024:
size_str = f"{bytes_total / 1024:.2f} KB"
else:
size_str = f"{bytes_total} bytes"

print(f"=== {name} ===")
print(f" Shape: {arr.shape}")
print(f" Dtype: {arr.dtype}")
print(f" Elements: {arr.size:,}")
print(f" Bytes/element: {arr.itemsize}")
print(f" Total memory: {size_str}")

return bytes_total


# Usage
image = np.random.randint(0, 256, (1920, 1080, 3), dtype=np.uint8)
features = np.random.randn(50000, 768).astype(np.float32)

memory_report(image, "HD Image")
print()
memory_report(features, "ML Features")

Output:

=== HD Image ===
Shape: (1920, 1080, 3)
Dtype: uint8
Elements: 6,220,800
Bytes/element: 1
Total memory: 5.93 MB

=== ML Features ===
Shape: (50000, 768)
Dtype: float32
Elements: 38,400,000
Bytes/element: 4
Total memory: 146.48 MB

Estimating Memory Before Allocation

Calculate required memory before creating large arrays:

import numpy as np

def estimate_memory(shape, dtype=np.float64):
"""Estimate memory needed for an array without creating it."""
dtype = np.dtype(dtype)
elements = np.prod(shape)
bytes_needed = elements * dtype.itemsize

gb = bytes_needed / (1024**3)
return bytes_needed, f"{gb:.2f} GB" if gb >= 1 else f"{bytes_needed / 1024**2:.2f} MB"


# Check before allocating
shape = (100000, 10000)

for dtype in [np.float64, np.float32, np.float16]:
bytes_needed, readable = estimate_memory(shape, dtype)
print(f"{dtype}: {readable}")

Output:

<class 'numpy.float64'>: 7.45 GB
<class 'numpy.float32'>: 3.73 GB
<class 'numpy.float16'>: 1.86 GB

By understanding NumPy's memory model and choosing appropriate data types, you can build applications that efficiently handle datasets ranging from megabytes to terabytes.