
Most beginners learn NumPy functions first: np.array(), np.mean(), np.reshape() and so on. But knowing the functions is only half the job. The way you use them decides whether your code runs in milliseconds or minutes, and whether it is easy to read or full of hidden bugs.
This guide covers NumPy best practices that data analysts, data scientists and Python developers use every day. Each tip comes with a short example so you can apply it straight away. If you need a quick reference of functions instead, read our NumPy cheat sheet.
Why NumPy Best Practices Matter
NumPy is fast because its operations run in optimised C code on whole arrays at once. When you write NumPy code like ordinary Python, with loops, lists and repeated copies, you lose that speed. Following a few simple habits helps you:
Run calculations much faster on large datasets
Use less memory
Avoid silent bugs caused by copies and data types
Write shorter code that is easier to read and review
1. Use Vectorization Instead of Python Loops
The most important NumPy best practice is to work on the whole array at once instead of looping over each value.
Avoid:
prices = np.array([100, 250, 400, 150])
with_tax = []
for p in prices:
with_tax.append(p * 1.18)Better:
with_tax = prices * 1.18The vectorized version is shorter and runs far faster on large arrays because the loop happens inside NumPy, not in Python.
2. Let Broadcasting Do the Work
Broadcasting lets NumPy combine arrays of different shapes without copying data. For example, you can subtract the mean of each column from a 2D array in one line:
data = np.random.default_rng(42).random((1000, 3))
centered = data - data.mean(axis=0)Here data.mean(axis=0) has shape (3,), and NumPy automatically applies it to every row. Learn the broadcasting rules once and you will rarely need to repeat or tile arrays manually.
3. Choose the Right Data Type (dtype)
By default NumPy uses float64 and int64. For large datasets, a smaller type can cut memory use in half or more.
ages = np.array([21, 34, 45, 28], dtype=np.int8) # values up to 127
scores = np.array([88.5, 92.1, 79.4], dtype=np.float32)
print(ages.nbytes, scores.nbytes)Pick the smallest type that safely holds your values. Be careful: if a value goes beyond the range of the type (for example, 200 in int8), it will overflow and give a wrong result.
4. Preallocate Arrays Instead of Appending
np.append() creates a new array every time you call it, so using it inside a loop is slow.
Avoid:
result = np.array([])
for i in range(10000):
result = np.append(result, i ** 2)Better:
result = np.empty(10000)
for i in range(10000):
result[i] = i ** 2
# Best: fully vectorized
result = np.arange(10000) ** 25. Understand Views vs Copies
Basic slicing returns a view, which shares memory with the original array. Changing the view changes the original too.
arr = np.array([1, 2, 3, 4, 5])
part = arr[1:4]
part[0] = 100
print(arr) # [ 1 100 3 4 5]If you need an independent array, make a copy explicitly:
part = arr[1:4].copy()Fancy indexing (using a list of indices) and boolean masks always return a copy. Knowing this difference prevents some of the most confusing bugs in NumPy code.
6. Use In-Place Operations for Large Arrays
Operations like arr = arr * 2 create a new array. For very large arrays, in-place operations save memory:
arr *= 2
np.add(arr, 5, out=arr)Use this when memory matters, but avoid it when you still need the original values.
7. Use the Modern Random Generator
The recommended way to create random numbers in NumPy is np.random.default_rng(), not the older np.random.seed() functions.
rng = np.random.default_rng(seed=42)
sample = rng.integers(1, 100, size=10)
noise = rng.normal(0, 1, size=5)A generator object with a fixed seed makes your results reproducible and keeps random state separate for different parts of your project.
8. Use the axis Argument for Aggregations
Instead of looping over rows or columns, pass axis to functions like sum, mean and max.
sales = np.array([[120, 340, 560],
[200, 150, 610]])
sales.sum(axis=0) # total per column (per product)
sales.sum(axis=1) # total per row (per month)9. Avoid Switching Between Lists and Arrays
Converting an array to a list with .tolist() and back again inside a workflow wastes time and memory. Keep your data in NumPy arrays (or Pandas DataFrames) until the final step, such as saving a report or sending JSON output.
10. Replace if-else Loops with Boolean Masks and np.where
Conditional logic can also be vectorized.
marks = np.array([35, 72, 48, 90, 60])
passed = marks[marks >= 40]
result = np.where(marks >= 40, "Pass", "Fail")For more than two conditions, use np.select().
11. Handle Missing Values with NaN-Aware Functions
A single NaN makes np.mean() or np.sum() return NaN. Use the NaN-aware versions instead:
values = np.array([10, np.nan, 30, 40])
np.nanmean(values) # 26.67
np.nansum(values) # 80.0Also note that NaN only works in float arrays, not integer arrays.
12. Check Shapes and Compare Floats Safely
Many NumPy errors come from arrays not having the shape you expect. Print or assert the shape at key steps:
assert X.shape[0] == y.shape[0], "Rows in X and y must match"When comparing decimal results, do not use ==, because tiny rounding differences can make equal values look different. Use np.isclose() or np.allclose():
np.isclose(0.1 + 0.2, 0.3) # TrueCommon NumPy Mistakes to Avoid
Mistake | Better Practice |
|---|---|
Looping over array values | Use vectorized operations |
Calling np.append() in a loop | Preallocate with np.empty() or np.zeros() |
Editing a slice and changing the original by accident | Use .copy() when you need a separate array |
Using float64 for everything | Choose a smaller dtype where it is safe |
Using np.random.seed() | Use np.random.default_rng() |
Using np.mean() on data with NaN | Use np.nanmean() |
Comparing floats with == | Use np.isclose() or np.allclose() |
How to Practise These NumPy Best Practices
Take a small dataset, such as sales or student marks, and first write the solution with loops. Then rewrite it using the tips above and compare the speed with %timeit in Jupyter Notebook. Doing this a few times will make vectorized thinking a habit.
Once you are comfortable, move on to Pandas, which is built on top of NumPy. Our guide on the differences between NumPy and Pandas explains when to use each, and top Python libraries for data science shows what to learn next.
Conclusion
Good NumPy code is vectorized, memory-aware and easy to read. Start with the biggest wins: replace loops with vectorized operations, avoid np.append() in loops, and understand views vs copies. Then add the other habits one by one as you work on real projects.
If you want to learn NumPy, Pandas, Machine Learning and real-world projects with trainer guidance, explore the Python course or the Data Science course at Brillica Services, available in classroom and live online batches.
Frequently Asked Questions
What are the most important NumPy best practices?
The most important ones are using vectorized operations instead of loops, using broadcasting, choosing the right dtype, preallocating arrays instead of appending, and understanding the difference between views and copies.
Why is NumPy faster than Python lists?
NumPy stores data in fixed-type arrays and runs operations in optimised C code on the whole array at once, while Python lists store separate objects and are processed one item at a time.
Should I use np.append() in a loop?
No. np.append() creates a new array every time, which is slow. Preallocate an array with np.empty() or np.zeros(), or better, use a vectorized operation.
What is the difference between a view and a copy in NumPy?
A view shares memory with the original array, so changes to it also change the original. A copy is independent. Basic slicing returns a view, while fancy indexing, boolean masks and .copy() return copies.
How do I generate random numbers in NumPy the recommended way?
Create a generator with np.random.default_rng(seed) and call methods such as integers(), random() or normal() on it. This is the recommended approach over the older np.random.seed() functions.
