Performance & Vectorization vs Loops
Reviewed & published by Brayan K
Vectorization is the practice of expressing a computation as operations on whole arrays instead of element-by-element Python loops — letting NumPy run the work as a single compiled C loop over contiguous memory rather than slow, interpreted Python. In this lesson you'll see why Python loops are slow, rewrite a loop as a one-line vectorized expression, preallocate instead of appending, lean on built-in aggregations, and understand why np.vectorize is convenience rather than speed.
Part of the free NumPy course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
What You'll Learn in This Lesson
- Why Python loops are slow on numbers
- How vectorization runs in compiled C
- Rewriting a loop as one expression
- Preallocating instead of appending
- Using built-in aggregations like .sum()
- Why np.vectorize is not a speed-up
1 Why Python Loops Are Slow
A Python for loop over numbers pays a tax on every iteration: it interprets bytecode and treats each number as a full Python object that must be unboxed, operated on, and reboxed. A NumPy array keeps raw numbers in contiguous memory and runs the whole operation as one compiled C loop, skipping all that overhead.
import numpy as np
data = np.arange(1, 6) # [1 2 3 4 5]
# Slow style: an explicit Python loop building a list
squares_loop = []
for x in data:
squares_loop.append(x ** 2)
print(squares_loop) # [1, 4, 9, 16, 25]
# Fast style: one vectorized expression, no Python loop
squares_vec = data ** 2
print(squares_vec) # [ 1 4 9 16 25]
# ✅ Expected output:
# [np.int64(1), np.int64(4), np.int64(9), np.int64(16), np.int64(25)]
# [ 1 4 9 16 25]Expected output: both produce 1, 4, 9, 16, 25 — identical results, but the vectorized version is far faster on big data.
2 Rewriting a Loop as a Vectorized Expression
Most element-wise loops collapse into a single expression. Whenever you see a loop that does the same arithmetic to each element, look for the array version: a + b, a * 2, or a combined formula applied to whole arrays at once.
import numpy as np
prices = np.array([10.0, 20.0, 30.0])
quantity = np.array([2, 1, 3])
# Loop version: total cost per line
totals_loop = []
for i in range(len(prices)):
totals_loop.append(prices[i] * quantity[i] * 1.1) # +10% tax
print(totals_loop) # [22.0, 22.0, 99.00000000000001]
# Vectorized version: one expression on whole arrays
totals_vec = prices * quantity * 1.1
print(totals_vec) # [22. 22. 99.]
print(np.allclose(totals_loop, totals_vec)) # True
# ✅ Expected output:
# [np.float64(22.0), np.float64(22.0), np.float64(99.00000000000001)]
# [22. 22. 99.]
# TrueExpected output: both give roughly [22, 22, 99], and np.allclose confirms they match with True.
3 Preallocate Instead of Appending
Calling np.append inside a loop is a trap: each call builds a brand-new array and copies everything, which is quadratic and painfully slow. If you truly need a loop, preallocate with np.zeros(n) once and fill slots by index. Better still, vectorize and skip the loop.
import numpy as np
n = 5
# Bad: append in a loop -> copies the whole array every time
slow = np.array([], dtype=int)
for i in range(n):
slow = np.append(slow, i * i)
print(slow) # [ 0 1 4 9 16]
# Good: preallocate, then fill by index (or skip the loop with np.arange(n)**2)
fast = np.zeros(n, dtype=int)
for i in range(n):
fast[i] = i * i
print(fast) # [ 0 1 4 9 16]
# ✅ Expected output:
# [ 0 1 4 9 16]
# [ 0 1 4 9 16]Expected output: both print [ 0 1 4 9 16] — same result, but the preallocated version avoids the costly per-step copies that np.append makes.
4 Built-in Aggregations & np.vectorize
Don't hand-roll a running total or maximum in Python. NumPy's built-in aggregations — .sum(), .mean(), .max(), .min() — run in C and are both shorter and dramatically faster than a manual loop.
import numpy as np
data = np.arange(1, 101) # 1..100
# Manual loop sum
total_loop = 0
for x in data:
total_loop += x
print(total_loop) # 5050
# Built-in aggregations — each is one call that runs in C
print(data.sum()) # 5050
print(data.mean()) # 50.5
# ✅ Expected output:
# 5050
# 5050
# 50.5Expected output: 5050 from both the loop and .sum(), then mean 50.5.
Beware of np.vectorize: it wraps a scalar function so it accepts arrays — handy for readability. But it still loops in Python internally, so it is not a performance tool. For real speed, express the logic with true NumPy operations (arithmetic, np.where, masks).
import numpy as np
arr = np.array([-2, -1, 0, 1, 2])
# Convenience: np.vectorize wraps a scalar function (still loops internally)
def clamp(x):
return 0 if x < 0 else x
vclamp = np.vectorize(clamp)
print(vclamp(arr)) # [0 0 0 1 2]
# Fast: a true vectorized operation does the same thing in C
print(np.where(arr < 0, 0, arr)) # [0 0 0 1 2]
# ✅ Expected output:
# [0 0 0 1 2]
# [0 0 0 1 2]Expected output: both print [0 0 0 1 2], but np.where runs in C while np.vectorize only looks fast.
🎯 YOUR TURN: Fill in the Blanks
Replace each ___ so the slow loop becomes a fast, vectorized one-liner that doubles every value and sums the result.
import numpy as np
data = np.array([1, 2, 3, 4])
# Vectorized: double every element with one expression (no loop)
doubled = data * ___ # fill in the multiplier -> [2 4 6 8]
print(doubled)
# Built-in aggregation instead of a manual loop sum
total = doubled.___() # fill in the method name -> 20
print(total)Expected output: [2 4 6 8] then 20. (Answers: 2, sum.)
⚠️ Common Errors & Quick Reference
❌ Looping over a huge array element by element
A Python for loop over millions of elements can be 50-100x slower than the array version.
✅ Fix: replace the loop with a single vectorized expression or a built-in aggregation.
❌ Expecting np.vectorize to speed things up
It only changes how the function is called; the work still happens in a Python-level loop.
✅ Fix: rewrite the logic with real NumPy ops like np.where, masks, or arithmetic.
| Slow Pattern | Fast Replacement |
|---|---|
| Loop doing arithmetic | a * b + c |
| Manual running total | arr.sum() |
| np.append in a loop | np.zeros(n) then fill |
| if/else per element | np.where(cond, a, b) |
🏆 Mini Challenge: Vectorize a Distance Calculation
Compute the Euclidean distance of each point from the origin, first with a loop, then with one vectorized expression, and confirm they match.
import numpy as np
xs = np.array([3.0, 5.0, 8.0])
ys = np.array([4.0, 12.0, 6.0])
# One vectorized expression replaces a per-point loop
dist = np.sqrt(xs**2 + ys**2)
print(dist) # [ 5. 13. 10.]
# ✅ Expected output:
# [ 5. 13. 10.]❓ Frequently Asked Questions
Lesson complete — you write fast NumPy now!
You know why Python loops are slow, how to rewrite them as vectorized expressions, when to preallocate, how to reach for built-in aggregations, and why np.vectorize is convenience rather than speed.
Practice quiz
Why is a NumPy vectorized operation faster than a Python for loop?
- It runs as one compiled C loop over contiguous memory
- It uses more RAM
- It skips some elements
- It runs on the GPU automatically
Answer: It runs as one compiled C loop over contiguous memory. NumPy runs the whole operation in compiled C over a contiguous block, avoiding per-element Python overhead.
What does 'vectorization' mean?
- Drawing vectors
- Expressing a calculation as operations on whole arrays
- Using a for loop
- Converting to a list
Answer: Expressing a calculation as operations on whole arrays. Vectorization replaces element-by-element loops with whole-array expressions.
Does np.vectorize make code run faster?
- Yes, it compiles to C
- Yes, it uses threads
- No, it still loops in Python internally
- Yes, always
Answer: No, it still loops in Python internally. np.vectorize is a convenience wrapper; it still loops in Python and is not a speed tool.
Why is calling np.append inside a loop slow?
- It uses the GPU
- It sorts each time
- It compresses data
- It copies the whole array on every call, giving quadratic cost
Answer: It copies the whole array on every call, giving quadratic cost. Each np.append builds a new array and copies all existing data, which is quadratic.
Which is the recommended fast alternative to a loop building squares?
- data ** 2
- a Python for loop with append
- np.vectorize(pow)
- map(lambda x: x*x, data)
Answer: data ** 2. data ** 2 is one vectorized expression that runs in C with no Python loop.
What is the fast way to total an array instead of a manual loop?
- A while loop
- arr.sum()
- np.vectorize(add)
- reduce in pure Python
Answer: arr.sum(). Built-in aggregations like arr.sum() run in C and are far faster than a manual loop.
Which truly fast operation replaces a per-element if/else?
- np.vectorize
- a list comprehension
- np.where(cond, a, b)
- a for loop
Answer: np.where(cond, a, b). np.where applies the conditional element-wise in C, the genuinely fast replacement.
If you must use a loop to fill values, what should you do first?
- Append each value
- Use np.vectorize
- Convert to a list
- Preallocate with np.zeros(n) and fill by index
Answer: Preallocate with np.zeros(n) and fill by index. Preallocating once and filling by index avoids the repeated copies that np.append causes.
How does a Python loop treat each number compared to a NumPy array element?
- As a boxed Python object that must be unboxed each iteration
- As raw C memory
- As a string
- As a GPU value
Answer: As a boxed Python object that must be unboxed each iteration. Python boxes each number as a full object, adding overhead that NumPy's raw contiguous storage avoids.
Both a loop and a vectorized version of the same math should produce...
- Different results
- The same results, but vectorized is faster
- An error
- Only integer results
Answer: The same results, but vectorized is faster. They compute identical results; vectorization just runs much faster on large data.
Continue this course
- Previous: File I/O: save, load, savetxt
- Next: Splitting Arrays: split, hsplit & vsplit — Divide one array into many with split, hsplit, vsplit, and array_split
- Quick reference: NumPy cheat sheet
Frequently asked questions
Why is NumPy faster than a Python for loop?
NumPy stores numbers in a single block of contiguous memory and runs each operation as one pre-compiled C loop. A Python for loop, by contrast, interprets bytecode and boxes every element as a full Python object on each iteration, which adds huge overhead. The result is often a 10-100x speed-up on large arrays.
What does 'vectorization' actually mean?
Vectorization means expressing a calculation as operations on whole arrays instead of element-by-element loops. You write one expression like a + b or arr * 2, and NumPy applies it to every element internally at C speed with no Python-level loop.
Does np.vectorize make my code faster?
No. np.vectorize is a convenience wrapper that lets a scalar function accept arrays, but it still loops in Python under the hood, so it is essentially as slow as a manual loop. Use it for readability, not speed — for real performance, rewrite the math using true NumPy operations.
Why should I preallocate instead of appending in a loop?
Repeatedly calling np.append inside a loop creates a brand-new array and copies all existing data every time, giving quadratic cost. Preallocating with np.zeros(n) (or np.empty) once and filling slots by index avoids the copies — and most of the time you can skip the loop entirely with a vectorized expression.