Skip to content
elevatedevco

Cheat sheet · Data & MLOps

NumPy cheat sheet

The key ideas on one page — plus common pitfalls and interview tips. Skim it before an interview, or print it for revision.


Cheat sheet

NumPy is the foundational array library for numerical computing in Python: a contiguous, typed n-dimensional array (ndarray) plus vectorized operations, broadcasting, and views over a strided memory buffer. Mastery means understanding dtype/memory layout, broadcasting rules, views vs copies, and writing loop-free vectorized code that runs in compiled C.

The ndarray & Memory Model

  • An ndarray is a contiguous block of homogeneous memory described by metadata: shape, dtype, strides, and a pointer to the data buffer. Most operations manipulate this metadata cheaply rather than copying data.
  • strides = bytes to step in each dimension to move one element along that axis; reshape/transpose/basic-slicing change shape and strides without copying data (a 'view').
  • Memory order: C-contiguous (row-major, last axis varies fastest) vs F-contiguous (column-major, Fortran). Check with arr.flags; it affects cache performance and whether ops can avoid copies.
  • dtype controls size & semantics: int8/16/32/64, uint*, float16/32/64, bool, complex64/128, datetime64/timedelta64, and structured dtypes. Default float is float64.
  • Default integer dtype is platform's pointer width: as of NumPy 2.0 it is int64 on all 64-bit systems (Windows included; pre-2.0 Windows defaulted to int32) and int32 on 32-bit systems.
  • nbytes = itemsize (bytes per element) x size (total elements). float32 halves memory and bandwidth vs float64 — key for large ML data.
  • Fixed-width integer types overflow/wrap silently: np.int8 wraps past 127. Object dtype (dtype=object) stores Python pointers and kills vectorized performance.

Creation & Reshaping

  • Constructors: np.array, np.zeros, np.ones, np.empty (uninitialized/fast), np.full, np.arange (start, stop, step; stop exclusive), np.linspace (count, endpoint-inclusive by default), np.eye/np.identity.
  • Random (modern API, 2026 best practice): rng = np.random.default_rng(seed); then rng.random(), rng.normal(), rng.integers(). The Generator API is faster, statistically better, and explicit about state — prefer it over legacy np.random.seed/rand.
  • reshape(-1) flattens; a single -1 means 'infer this dimension from the total size and the rest'. ravel() returns a view when possible; flatten() always copies.
  • Add/remove axes: arr[:, None] or np.newaxis inserts an axis; np.expand_dims / np.squeeze. np.transpose / arr.T / np.moveaxis / np.swapaxes permute axes (views).
  • Combine: np.concatenate (existing axis), np.stack (new axis), np.vstack/hstack/dstack, np.r_/np.c_. Split with np.split/array_split.
  • np.asarray avoids a copy when the input is already a compatible ndarray; np.array(..., copy=True) forces a copy. (In NumPy 2.0, copy=False raises if a copy is unavoidable; use np.asarray for 'copy only if needed'.)

Indexing: Views vs Copies (high-yield)

  • Basic slicing (arr[1:3, ::2]) returns a VIEW — mutating it mutates the original. This is the #1 interview gotcha.
  • Fancy/advanced indexing with integer arrays or boolean masks returns a COPY: arr[[0, 2]], arr[arr > 0].
  • Boolean masking: arr[mask] selects; arr[mask] = 0 assigns in place. np.where(cond, a, b) is the vectorized ternary; np.where(cond) alone returns index tuples.
  • Combine conditions with & | ~ and parentheses (NOT Python and/or, which raise 'ambiguous truth value' on arrays): arr[(arr > 0) & (arr < 10)].
  • np.take / np.put, np.argwhere / np.nonzero return index positions. np.clip(arr, lo, hi) bounds values element-wise.
  • For independence when you sliced, call .copy() explicitly; use np.shares_memory(a, b) to test aliasing.

Broadcasting & Vectorization

  • Broadcasting rule: align shapes from the RIGHT (trailing axes). Dimensions are compatible if equal or one of them is 1; size-1 dims are stretched virtually with no data copy. Missing leading dims are treated as 1.
  • Example: (3,1) + (1,4) -> (3,4); (256,256,3) * (3,) -> scales channels. Incompatible (3,) + (4,) raises ValueError.
  • Vectorize: replace Python loops with array ops — they run in compiled C and are typically 10-100x faster. np.vectorize is NOT fast; it is a Python-loop convenience wrapper.
  • Ufuncs (np.add, np.exp, np.maximum, ...) operate element-wise with optional out= to write in place and avoid allocations, plus .reduce/.accumulate/.outer methods.
  • Use out= and in-place operators (+=, *=) to cut allocations in hot loops. np.einsum('ij,jk->ik', A, B) expresses contractions/reductions compactly.
  • np.newaxis enables outer-product-style ops: a[:, None] - b[None, :] builds a pairwise difference matrix.

Aggregations, Axis & NaN Handling

  • Reductions take axis: arr.sum(axis=0) collapses rows -> per-column result; axis=1 -> per-row. axis=None (default) reduces everything to a scalar.
  • keepdims=True preserves reduced dims as size 1 so the result broadcasts back: x - x.mean(1, keepdims=True).
  • Mental model: axis is the dimension that DISAPPEARS. For a (rows, cols) matrix, axis=0 means 'collapse down the rows' (column stats).
  • NaN propagates through normal reductions (any NaN -> NaN). Use np.nansum/nanmean/nanstd to ignore NaNs; detect with np.isnan, np.isfinite.
  • Float comparison needs tolerance: np.isclose / np.allclose, never ==, because e.g. 0.1 + 0.2 != 0.3.
  • Cumulative & order ops: cumsum, cumprod, argmin/argmax (index of extreme), sort/argsort, unique, bincount, percentile/quantile.

Linear Algebra & Performance

  • Matrix multiply: @ operator or np.matmul; np.dot for 1-D/2-D dot products. Use * for element-wise. np.linalg: inv, solve, det, eig/eigh, svd, qr, cholesky, norm, pinv, lstsq.
  • Prefer np.linalg.solve(A, b) over inv(A) @ b — it is faster and numerically more stable. These call optimized, multithreaded BLAS/LAPACK (OpenBLAS/MKL); performance depends on the linked backend.
  • Avoid building large intermediates; prefer fused ops, out=, views, and float32 where precision allows to halve bandwidth.
  • np.einsum and np.tensordot handle general tensor contractions; einsum supports optimize=True (and np.einsum_path) to reorder for fewer FLOPs.
  • Interop: arrays share buffers with PyTorch/JAX/pandas via the buffer/DLPack protocols (torch.from_numpy is zero-copy on CPU). np.frombuffer/np.memmap map bytes/disk data without loading into RAM.
  • NumPy 2.x (2.0 released June 2024) cleaned the C-API and namespace, removed aliases (np.float_, np.int0, np.bool8), and changed scalar promotion (NEP 50). The Array API standard is now built into the main namespace (the experimental np.array_api module was removed); use array-api-compat for backend-agnostic code across NumPy/CuPy/JAX/PyTorch.