datarekha
Python Easy Asked at GoogleAsked at MetaAsked at Amazon

When would you use a Python list versus a NumPy array, and what are the performance trade-offs?

The short answer

Python lists are heterogeneous, pointer-based, and general-purpose. NumPy arrays are homogeneous, stored as contiguous typed memory, and support vectorised operations that run at C speed. For numerical work on more than a few hundred elements, NumPy is almost always faster and more memory-efficient.

How to think about it

In a data or ML interview this question is really probing whether you understand why NumPy is fast, not just that it is. The whole answer is memory layout. A Python list stores an array of pointers to arbitrary objects; a NumPy array stores raw values of one type, packed back to back like a C array.

That difference cascades. A list of a million floats is a million separate float objects scattered across the heap (each with a type tag and a reference count), plus a million pointers to reach them. A NumPy float64 array is a million doubles in one contiguous block — 8 bytes each, no headers, no pointers. Which buys three things: far less memory, cache-friendly access the CPU can prefetch, and operations that dispatch to compiled BLAS/LAPACK kernels entirely outside the interpreter loop.

A worked example

import sys
import numpy as np

# Same 1000 floats, very different footprints
small_list = [1.0] * 1000
small_arr = np.ones(1000, dtype=np.float64)
print(f"list (1000 floats) : {sys.getsizeof(small_list)} bytes (pointers only)")
print(f"numpy(1000 floats) : {small_arr.nbytes} bytes (raw doubles)")

# NumPy gives vectorised math for free — one C call, no Python loop
arr = np.array([1.0, 4.0, 9.0, 16.0])
print("sqrt via numpy     :", np.sqrt(arr))
print("arr * 2 + 1        :", arr * 2.0 + 1.0)
list (1000 floats) : 8056 bytes (pointers only)
numpy(1000 floats) : 8000 bytes (raw doubles)
sqrt via numpy     : [1. 2. 3. 4.]
arr * 2 + 1        : [ 3.  9. 19. 33.]

There’s a subtle catch in those sizes: the list reports 8,056 bytes, but that’s only the pointer arraysys.getsizeof doesn’t count the thousand actual float objects it points to, each another ~24 bytes. So the list’s real footprint is several times the array’s, not roughly equal. And the last two lines show what that layout buys: np.sqrt(arr) and arr * 2.0 + 1.0 transform the whole array in a single compiled call — no Python loop, the operation NumPy was built for. On a million elements, that vectorised form is routinely tens of times faster than a list comprehension.

When to stick with a plain list

  • Heterogeneous elements: [42, "hello", None, True].
  • Append-heavy work where you don’t know the final size up front.
  • Small collections, where NumPy’s overhead outweighs the win.
  • Non-numeric data — strings, objects, nested dicts.
NeedUse
general-purpose mixed datalist
numeric computation / ML featuresnumpy.ndarray
tabular data with named columnspandas.DataFrame
typed compact sequence, stdlib onlyarray.array
Learn it properly Lists

Keep practising

All Python questions

Explore further

Skip to content