AlgoInsight

Q&A

How to perform arithmetic operations in NumPy

By AlgoInsight Team2026-06-07arraysnumpypython

Overview

NumPy's core type is the ndarray, and its superpower is vectorized arithmetic: an operation written once applies to every element, executed in optimized native code over contiguous memory. This replaces slow Python for loops and is the foundation of scientific computing and ML in Python.

graph LR
    A["a = [1,2,3]"] --> C["a * 2"]
    C --> D["[2,4,6] - element-wise, no loop"]
    A --> E["a + b - element-wise"]
    A --> F["broadcasting: different shapes align"]
    style D fill:#1e1b4b,color:#fff

Element-wise Operations

Operators apply to each element; arrays of the same shape combine position by position:

import numpy as np
a = np.array([1, 2, 3])
b = np.array([10, 20, 30])

a + b      # [11, 22, 33]
a * b      # [10, 40, 90]   (element-wise, NOT matrix multiply)
b - a      # [9, 18, 27]
b / a      # [10., 10., 10.]
a ** 2     # [1, 4, 9]
a % 2      # [1, 0, 1]

Contrast with Python lists, where [1,2,3] * 2 repeats the list - NumPy does math.

Scalar Operations (broadcasting a scalar)

A scalar applies to every element:

a + 100    # [101, 102, 103]
a * 2      # [2, 4, 6]
1 / a      # [1.0, 0.5, 0.333...]

Broadcasting (different shapes)

NumPy "broadcasts" smaller arrays across larger ones when shapes are compatible - aligning dimensions from the right, stretching size-1 dimensions:

matrix = np.array([[1, 2, 3],
                   [4, 5, 6]])        # shape (2, 3)
row = np.array([10, 20, 30])          # shape (3,)

matrix + row
# [[11, 22, 33],
#  [14, 25, 36]]    - row added to each matrix row

Broadcasting avoids manually tiling arrays and is a key NumPy idiom. Shapes are compatible when, compared from the trailing dimension, they're equal or one of them is 1.

Universal Functions (ufuncs)

NumPy provides fast element-wise math functions:

np.sqrt(a)        # [1., 1.414, 1.732]
np.exp(a)         # e^x for each element
np.log(b)
np.sin(a)
np.add(a, b)      # same as a + b
np.maximum(a, b)  # element-wise max

Aggregations

Reduce an array to summary values, optionally along an axis:

arr = np.array([[1, 2, 3], [4, 5, 6]])
arr.sum()          # 21  (all elements)
arr.sum(axis=0)    # [5, 7, 9]  (column sums)
arr.sum(axis=1)    # [6, 15]    (row sums)
arr.mean()         # 3.5
arr.max(), arr.min(), arr.std()

axis=0 collapses rows (operate down columns); axis=1 collapses columns (operate across rows).

Matrix Multiplication vs Element-wise

A crucial distinction: * is element-wise; @ (or np.dot) is true matrix multiplication:

A = np.array([[1, 2], [3, 4]])
B = np.array([[5, 6], [7, 8]])

A * B      # element-wise: [[5,12],[21,32]]
A @ B      # matrix product: [[19,22],[43,50]]

Why Vectorization Is Fast

A Python loop executes interpreted bytecode per element; NumPy executes one compiled C loop over a contiguous typed buffer, with no per-element Python overhead (and often SIMD acceleration). For large arrays this is often 10–100× faster - and the code is shorter.

Why This Matters in Interviews

Data-science/Python interviews use this to check NumPy fluency. Key points: arithmetic is element-wise/vectorized (no loops), the * (element-wise) vs @ (matrix multiply) distinction (a classic trap), broadcasting rules for differing shapes, aggregations with axis, ufuncs, and why vectorization beats Python loops (compiled C over contiguous memory). The axis semantics and broadcasting are the highest-value details.

Common Pitfalls

References

More answers