NumPy: Array Creation with zeros, ones, arange, linspace

NumPy arrays are the building blocks of almost every data science and machine learning task in Python. This guide is your quick reference for creating one: zeros, ones, arange, linspace, eye, full, empty, the modern random generator, plus indexing basics, with tested output for every single method.

“The art of programming is the art of organizing complexity.”

Edsger Dijkstra

Last Updated: July 2026 | Tested on: Python 3.14.6, NumPy 2.4.6 | Difficulty: Intermediate | Reading Time: 22 minutes

NumPy hands you about a dozen ways to make an array, and picking the right one is the difference between clean numeric code and twenty minutes of fighting shape errors. Think of it like a sweet shop counter. Sometimes you grab a box of laddoos that is already packed (that is np.array from a list), sometimes you want an empty tray of a known size to fill later (that is np.zeros or np.empty), and sometimes you just want a numbered row of slots (that is np.arange). This post is the whole counter. Every method, when to reach for it, and the exact output you should see.

Quick refresher from the NumPy introduction: a NumPy array stores its data in one solid block of memory with a single data type. That is why math on millions of elements runs at C speed instead of plain Python speed. Pandas, Matplotlib, and scikit-learn all expect NumPy arrays underneath, so getting comfortable with creating NumPy arrays is not optional. It is the first thing you do in every notebook.

The cheat sheet below is the fast answer most people came for. Skim it, grab the function you need, and the worked examples after it show every method with real, tested output. No theory wall to scroll past first.

Array Creation Cheat Sheet

Randomrng.randomdefault_rngrng.integersrng.standard_normalFilled Arraysnp.zeros /np.onesnp.emptynp.fullnp.eye /np.identityRange-basednp.arangenp.linspacenp.logspaceFrom Existing Datanp.arraynp.asarraynp.frombuffer🔢 NumPy Array CreationPython NumPy Arrays: Choosing a Creation Function by Data Source

Tip: click the diagram to open it full screen, then use the zoom and pan controls for a closer look.

The diagram sorts the ways to create NumPy arrays by where the data comes from: from existing data (np.array(), np.asarray()), filled with a value (np.zeros(), np.ones(), np.full()), range based (np.arange(), np.linspace()), and random (np.random.default_rng()). Figure out which group your job falls into and you have found the right function in seconds. Here is the same idea as a lookup table you can scan.

FunctionWhat it makesQuick exampleResult
np.array(seq)Array from a list or tuplenp.array([1, 2, 3])[1 2 3]
np.zeros(shape)All zerosnp.zeros(3)[0. 0. 0.]
np.ones(shape)All onesnp.ones(3)[1. 1. 1.]
np.full(shape, v)All the same valuenp.full(3, 7)[7 7 7]
np.empty(shape)Uninitialized (fast, garbage values)np.empty(3)[random junk]
np.arange(a, b, step)Range by step sizenp.arange(0, 6, 2)[0 2 4]
np.linspace(a, b, n)N evenly spaced pointsnp.linspace(0, 1, 3)[0. 0.5 1. ]
np.logspace(a, b, n)N points on a log scalenp.logspace(0, 2, 3)[1. 10. 100.]
np.eye(n)Identity matrixnp.eye(2)[[1. 0.] [0. 1.]]
np.diag(seq)Diagonal matrixnp.diag([1, 2])[[1 0] [0 2]]
rng.random(n)Random floats in [0, 1)rng.random(2)[0.77 0.44]
NumPy array creation cheat sheet. Outputs shown are real runs on NumPy 2.4.6. Random values use a seeded generator, so yours match only if you seed the same way.

Prerequisites

Finish the NumPy introduction first. You need NumPy installed (run pip install numpy) and a basic feel for what an array is. Everything here was tested on NumPy 2.4.6 with Python 3.14.6.

From Existing Data

The most common way to make NumPy arrays is to hand over a list you already have. Think of it like pouring loose Lego bricks from a bag into a fitted tray: np.array() takes your Python list and packs it into one tight block of memory with a single data type. There is also np.asarray(), which is the polite version. If you give it something that is already a NumPy array, it does not waste time making a copy. It just hands the same array back.

📄 from_data.py: creating arrays from Python sequences

import numpy as np

# From list
scores = np.array([85, 92, 78, 95, 88])
print(f"From list: {scores}  dtype: {scores.dtype}")

# From nested lists (2D)
matrix = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
print(f"2D:\n{matrix}")

# From tuple
coords = np.array((3.5, 7.2, 1.8))
print(f"\nFrom tuple: {coords}  dtype: {coords.dtype}")

# Forcing a specific dtype
forced_float = np.array([1, 2, 3], dtype=np.float32)
print(f"Forced float32: {forced_float}  dtype: {forced_float.dtype}")

# From range
from_range = np.array(range(10))
print(f"From range: {from_range}")

# asarray does NOT copy if the input is already an ndarray
existing = np.array([1, 2, 3])
same = np.asarray(existing)   # no copy, hands the same array back
copy = np.array(existing)     # always a fresh copy
print(f"\nasarray returns the same object: {same is existing}")
print(f"asarray shares memory:           {np.shares_memory(same, existing)}")
print(f"array shares memory:             {np.shares_memory(copy, existing)}")

▶ Output

From list: [85 92 78 95 88]  dtype: int64
2D:
[[1 2 3]
 [4 5 6]
 [7 8 9]]

From tuple: [3.5 7.2 1.8]  dtype: float64
Forced float32: [1. 2. 3.]  dtype: float32
From range: [0 1 2 3 4 5 6 7 8 9]

asarray returns the same object: True
asarray shares memory:           True
array shares memory:             False

What happened here: NumPy guessed the data type for you each time. A list of whole numbers became int64, a tuple of decimals became float64, and passing dtype=np.float32 forced the type when you wanted to save memory. The last three lines are the real lesson. Because existing is already a NumPy array, np.asarray() hands the very same object straight back (same is existing is True), so they share memory. np.array() always builds a fresh copy, so it shares nothing.

Use np.shares_memory() to check this rather than poking at .base, since a no-copy asarray returns the same object and has no base. The takeaway: reach for asarray in functions that accept both lists and arrays so you skip pointless copies.

Filled Arrays: zeros, ones, full, empty

Sometimes you do not have the data yet. You just want a tray of the right size to fill in later. That is what these four are for: pre-filled NumPy arrays in one call. Picture a fresh ice cube tray: np.zeros gives you every slot set to 0, np.ones sets every slot to 1, np.full sets every slot to whatever value you name, and np.empty hands you the tray without rinsing it, so the slots hold whatever leftover numbers were sitting in that memory. The _like versions copy the shape of an array you already have, which saves you from typing the dimensions twice.

📄 filled_arrays.py: pre-filled array constructors

import numpy as np

# zeros: good for accumulators and masks
z1d = np.zeros(5)
z2d = np.zeros((3, 4))
print(f"zeros(5): {z1d}")
print(f"zeros(3,4):\n{z2d}\n")

# ones: good for weight init and masks
o = np.ones((2, 3))
print(f"ones(2,3):\n{o}\n")

# full: fill with a specific value
f = np.full((2, 3), fill_value=42)
print(f"full(2,3, 42):\n{f}\n")

# empty: uninitialized (garbage values, but fast)
e = np.empty(3)
print(f"empty(3): {e}  (values are leftover memory, yours will differ!)\n")

# _like variants: match the shape of an existing array
template = np.array([[1, 2, 3], [4, 5, 6]])
zl = np.zeros_like(template)
ol = np.ones_like(template)
print(f"zeros_like:\n{zl}")
print(f"ones_like:\n{ol}")

▶ Output

zeros(5): [0. 0. 0. 0. 0.]
zeros(3,4):
[[0. 0. 0. 0.]
 [0. 0. 0. 0.]
 [0. 0. 0. 0.]]

ones(2,3):
[[1. 1. 1.]
 [1. 1. 1.]]

full(2,3, 42):
[[42 42 42]
 [42 42 42]]

empty(3): [0.00000000e+000 0.00000000e+000 9.12488124e+192]  (values are leftover memory, yours will differ!)

zeros_like:
[[0 0 0]
 [0 0 0]]
ones_like:
[[1 1 1]
 [1 1 1]]

What happened here: Notice the dots. zeros and ones default to floats (0. and 1.), while full with the integer 42 stayed an integer. The empty(3) line is the interesting one. Those numbers look like nonsense because they are: np.empty grabs a chunk of memory and does not bother resetting it, so you see whatever was there before. Your three values will not match the ones above, and that is the whole point. Only use empty when you are about to overwrite every slot anyway, because skipping the reset makes it a hair faster than zeros. Finally, zeros_like and ones_like copied the 2×3 shape (and the integer dtype) of template without you spelling out the dimensions.

Sequences: arange, linspace, logspace

These three build a row of numbers for you. The easiest way to keep them straight: arange is for when you know the step (count by 2s, by 0.5s), linspace is for when you know how many points you want spread evenly between two ends, and logspace does the same but on a log scale. It is like planning fence posts. With arange you say how far apart the posts are. With linspace you say how many posts you want, and NumPy spaces them out for you.

📄 sequences.py: generating number sequences

import numpy as np

# arange: like range() but returns an ndarray and supports floats
print(f"arange(5):       {np.arange(5)}")
print(f"arange(2,10):    {np.arange(2, 10)}")
print(f"arange(0,1,0.2): {np.arange(0, 1, 0.2)}")
print(f"arange(10,0,-2): {np.arange(10, 0, -2)}")

# linspace: N evenly spaced points (includes the endpoint)
print(f"\nlinspace(0,1,5):   {np.linspace(0, 1, 5)}")
print(f"linspace(0,10,3):  {np.linspace(0, 10, 3)}")

# Without endpoint
print(f"linspace(0,1,4, endpoint=False): {np.linspace(0, 1, 4, endpoint=False)}")

# logspace: logarithmically spaced
print(f"\nlogspace(0,3,4): {np.logspace(0, 3, 4)}")
# 10^0=1, 10^1=10, 10^2=100, 10^3=1000

# When to use which?
print("\n--- Decision Guide ---")
print("arange:   known step size (every 0.5, every 2)")
print("linspace: known number of points (100 points from 0 to pi)")
print("logspace: logarithmic scale (learning rates: 0.001 to 1)")

▶ Output

arange(5):       [0 1 2 3 4]
arange(2,10):    [2 3 4 5 6 7 8 9]
arange(0,1,0.2): [0.  0.2 0.4 0.6 0.8]
arange(10,0,-2): [10  8  6  4  2]

linspace(0,1,5):   [0.   0.25 0.5  0.75 1.  ]
linspace(0,10,3):  [ 0.  5. 10.]
linspace(0,1,4, endpoint=False): [0.   0.25 0.5  0.75]

logspace(0,3,4): [   1.   10.  100. 1000.]

--- Decision Guide ---
arange:   known step size (every 0.5, every 2)
linspace: known number of points (100 points from 0 to pi)
logspace: logarithmic scale (learning rates: 0.001 to 1)

What happened here: One detail trips people up: arange(2, 10) stops at 9, not 10, exactly like Python’s own range(). The stop value is never included. linspace is the opposite. It does include the endpoint by default, which is why linspace(0, 1, 5) lands neatly on 1., and you turn that off with endpoint=False. The logspace(0, 3, 4) call returned 1, 10, 100, 1000 because it raises 10 to each evenly spaced power, which is exactly what you want when you sweep learning rates or anything that grows by factors of ten.

Identity and Diagonal Matrices

An identity matrix is the number 1 of the matrix world. Multiply any matrix by it and you get the same matrix back, untouched, the same way multiplying a number by 1 leaves it alone. np.eye builds one: ones running down the diagonal, zeros everywhere else. np.diag is the handy two-way tool. Give it a flat list and it spreads those values down the diagonal of a fresh square matrix. Give it a matrix and it pulls the diagonal back out as a flat array.

📄 identity_diagonal.py: special matrices for linear algebra

import numpy as np

# Identity matrix: ones on the diagonal, zeros elsewhere
eye3 = np.eye(3)
print(f"Identity 3x3:\n{eye3}\n")

# Non-square identity
eye23 = np.eye(2, 3)
print(f"Identity 2x3:\n{eye23}\n")

# Diagonal matrix from values
diag = np.diag([1, 2, 3, 4])
print(f"Diagonal from [1,2,3,4]:\n{diag}\n")

# Extract the diagonal from an existing matrix
matrix = np.array([[10, 20, 30], [40, 50, 60], [70, 80, 90]])
extracted = np.diag(matrix)
print(f"Extracted diagonal: {extracted}")

# Why identity matters: A @ I = A (multiplying by identity changes nothing)
A = np.array([[1, 2], [3, 4]])
I = np.eye(2)
print(f"\nA @ I = A: {np.array_equal(A @ I, A)}")

▶ Output

Identity 3x3:
[[1. 0. 0.]
 [0. 1. 0.]
 [0. 0. 1.]]

Identity 2x3:
[[1. 0. 0.]
 [0. 1. 0.]]

Diagonal from [1,2,3,4]:
[[1 0 0 0]
 [0 2 0 0]
 [0 0 3 0]
 [0 0 0 4]]

Extracted diagonal: [10 50 90]

A @ I = A: True

What happened here: np.diag wore both hats. Fed a list [1, 2, 3, 4], it built a 4×4 matrix with those values on the diagonal. Fed the 3×3 matrix, it grabbed 10, 50, 90, the values running corner to corner. The last line proves the identity property: A @ I (the @ is matrix multiplication) gives back exactly A, so np.array_equal reports True. That is why identity matrices show up all over linear algebra as the safe starting point that changes nothing.

Random Arrays: The Modern API

Random NumPy arrays power everything from shuffling a dataset to splitting train and test sets. The modern way to get them in NumPy is np.random.default_rng(), which builds a generator object you then ask for numbers. Give it a seed and it becomes repeatable: same seed, same numbers every run. Think of the seed like the recipe number on a coffee machine. Punch in recipe 42 and you get the exact same cup today, tomorrow, and on your teammate’s laptop. That is what makes results you can reproduce.

The example below also runs a small lucky draw over a team of five friends, Rahul, Anvi, Aviraj, Aditi, and Anvay, to show how choice samples from any array you give it.

📄 random_arrays.py: NumPy’s modern random generator

import numpy as np

# Modern API: create a Generator with a seed for reproducibility
rng = np.random.default_rng(seed=42)

# Uniform random floats [0, 1)
print(f"random(5): {rng.random(5).round(3)}")

# Random integers (low inclusive, high exclusive)
print(f"integers(1,10, size=6): {rng.integers(1, 10, size=6)}")

# Normal (Gaussian) distribution
print(f"normal(mean=100, std=15, n=5): {rng.normal(100, 15, 5).round(1)}")

# Random choice from an array
names = ["Rahul", "Anvi", "Aviraj", "Aditi", "Anvay"]
print(f"choice: {rng.choice(names, size=3, replace=False)}")

# Shuffle (in-place)
deck = np.arange(1, 11)
rng.shuffle(deck)
print(f"shuffled: {deck}")

# 2D random arrays
print(f"\nRandom 3x3 matrix:\n{rng.random((3, 3)).round(2)}")

# Reproducing results: same seed means same numbers
rng1 = np.random.default_rng(42)
rng2 = np.random.default_rng(42)
print(f"\nSame seed, same results: {np.array_equal(rng1.random(5), rng2.random(5))}")

▶ Output

random(5): [0.774 0.439 0.859 0.697 0.094]
integers(1,10, size=6): [5 9 7 7 7 8]
normal(mean=100, std=15, n=5): [ 99.7  87.2 113.2 111.7 101. ]
choice: ['Aviraj' 'Anvi' 'Aditi']
shuffled: [ 7 10  2  6  5  1  9  8  4  3]

Random 3x3 matrix:
[[0.35 0.97 0.89]
 [0.78 0.19 0.47]
 [0.04 0.15 0.68]]

Same seed, same results: True

What happened here: Every number above came from seed 42, so if you run the same code on NumPy 2.4.6 you will see these exact values too. That is what seeding gives you: your notebook, a bug report, and a teammate’s machine all agree. The generator gave us uniform floats with random, whole numbers with integers (low included, high excluded, just like arange), a bell curve around 100 with normal, three lucky draw winners (Aviraj, Anvi, and Aditi this time) with choice, and a reshuffled deck with shuffle.

The final True confirms it: two generators built from the same seed walk through the exact same sequence. If you ever want fresh numbers each run, just create the generator with no seed.

Use the new generator, not the old one. You will still see np.random.rand() and np.random.seed() in older tutorials. Those touch one shared global state, which causes quiet bugs when different parts of your code reseed it. The np.random.default_rng() generator has been the recommended approach since NumPy 1.17 and is still current in NumPy 2.4.6 (at the time of writing). Give each piece of code its own generator and your random behavior stays predictable.

Indexing and Slicing Basics

Once you have NumPy arrays in hand, you need to reach into them. NumPy indexing feels like Python lists at first: arr[0] is the first item, arr[-1] is the last, arr[1:4] is a slice. Then it adds two extra tricks. For 2D arrays you give a row and a column in one bracket, like a seat number at a cinema (matrix[1, 2] means row 1, seat 2). And boolean indexing lets you keep only the items that pass a test, the way you might highlight every contact in your phone whose birthday is this month.

📄 indexing.py: accessing elements and slices

import numpy as np

# 1D indexing: same as Python lists
arr = np.array([10, 20, 30, 40, 50])
print(f"arr[0]:    {arr[0]}")
print(f"arr[-1]:   {arr[-1]}")
print(f"arr[1:4]:  {arr[1:4]}")
print(f"arr[::2]:  {arr[::2]}")   # Every other element

# 2D indexing: row, column
matrix = np.array([[1, 2, 3],
                    [4, 5, 6],
                    [7, 8, 9]])

print(f"\nmatrix[0, 0]: {matrix[0, 0]}")   # Row 0, Col 0
print(f"matrix[1, 2]: {matrix[1, 2]}")     # Row 1, Col 2
print(f"matrix[0]:    {matrix[0]}")         # Entire row 0
print(f"matrix[:, 1]: {matrix[:, 1]}")      # Entire column 1

# Slicing 2D
print(f"\nTop-left 2x2:\n{matrix[:2, :2]}")
print(f"Last two rows:\n{matrix[1:, :]}")

# Boolean indexing (filtering)
data = np.array([15, 28, 9, 34, 22, 7, 41])
mask = data > 20
print(f"\nData: {data}")
print(f"Mask (>20): {mask}")
print(f"Filtered:   {data[mask]}")

# Fancy indexing (index with an array)
indices = np.array([0, 3, 4])
print(f"Fancy [{indices}]: {data[indices]}")

▶ Output

arr[0]:    10
arr[-1]:   50
arr[1:4]:  [20 30 40]
arr[::2]:  [10 30 50]

matrix[0, 0]: 1
matrix[1, 2]: 6
matrix[0]:    [1 2 3]
matrix[:, 1]: [2 5 8]

Top-left 2x2:
[[1 2]
 [4 5]]
Last two rows:
[[4 5 6]
 [7 8 9]]

Data: [15 28  9 34 22  7 41]
Mask (>20): [False  True False  True  True False  True]
Filtered:   [28 34 22 41]
Fancy [[0 3 4]]: [15 34 22]

What happened here: The 1D slices behave just like Python lists, so nothing new there. The 2D part is where NumPy shines. matrix[:, 1] grabbed the whole second column in one short expression, which plain Python lists cannot do without a loop. Boolean indexing is the workhorse you will use most: data > 20 produces a mask of True and False, and feeding that mask back in with data[mask] keeps only the items where it was True. Fancy indexing does the same trick with a list of positions, pulling items 0, 3, and 4 in the order you asked. We go much deeper on all of this in the NumPy indexing guide.

arange vs linspace, Head to Head

These two cause the most confusion, so here they are side by side. Picture marking a running track: arange paints a line every 10 meters and stops wherever the track runs out, while linspace divides the track into exactly the number of marks you asked for, first mark on the start line and last mark on the finish. The one-line rule: if you know the gap between values, use arange. If you know how many values you want, use linspace.

Questionnp.arangenp.linspace
You specifystart, stop, step sizestart, stop, number of points
Includes the stop value?No (stop is excluded)Yes (by default)
Best for floats?Risky (rounding can add or drop a point)Safe (exact count and endpoints)
Typical useCounting by a fixed stepPlotting, evenly spaced samples
The Python way: for anything you will plot or feed into a math function, reach for linspace. You almost always know how many points you want on a chart (say 100 across the x axis), and linspace guarantees you get exactly that many with clean endpoints. Save arange for plain integer counting where the step is obvious.

Common Mistakes

Mistake 1: Using arange with floats and expecting exact endpoints

📄 arange_floats.py: floating point makes the count unpredictable

import numpy as np

# BAD: floating point can cause surprises
print(np.arange(0, 1, 0.3))  # Does it stop at 0.6 or sneak in 0.9?
# [0.  0.3 0.6 0.9]

# GOOD: use linspace when you need exact endpoints
print(np.linspace(0, 0.9, 4))
# [0.  0.3 0.6 0.9]

Why this bites: with a float step like 0.3, NumPy cannot always tell whether the last value squeaks under the stop or not, because 0.3 is not stored exactly in binary. Change the step a touch and you might get four values one time and three the next, which quietly breaks code that expected a fixed length. linspace sidesteps the whole problem because you tell it the count up front, so you always get exactly four points landing cleanly on the endpoints.

Mistake 2: Forgetting the tuple for a 2D shape

📄 shape_tuple.py: 2D shapes need a tuple, not two arguments

import numpy as np

# BAD: passing two ints where a shape tuple is expected
# np.zeros(3, 4)  # this is read as shape=3, dtype=4 and fails

# GOOD: wrap the dimensions in a tuple
print(np.zeros((3, 4)).shape)  # (3, 4)

# Note: 1D is fine with just an int
print(np.zeros(5).shape)       # (5,)

Why this bites: np.zeros reads its first argument as the shape and its second as the data type. So np.zeros(3, 4) does not mean a 3 by 4 grid, it tries to use 4 as a dtype and throws an error. The fix is one pair of parentheses: np.zeros((3, 4)) passes a single tuple as the shape. A 1D array is the only case where a bare integer works, since there is just one dimension to describe.

Conclusion

That is the whole sweet counter. Reach for np.array when you already have the data, np.zeros or np.ones or np.full when you want a tray pre-filled with one value, np.empty only when you are about to overwrite everything, np.arange when you know the step, and np.linspace when you know the count. For matrix work, np.eye and np.diag cover identity and diagonal matrices, and np.random.default_rng() is your modern, repeatable source of random data.

If you remember just two things, make them these: seed your random generator so results reproduce, and prefer linspace over arange for floats so your NumPy arrays always have the length you expect. Next up, the NumPy indexing guide takes the slicing and boolean tricks you saw here and pushes them much further, and after that broadcasting and vectorization show you how NumPy does math on whole arrays without writing a single loop. For every post in this series, from Python basics to full machine learning projects, visit the Python + AI/ML tutorial series home.

Practice Exercises

  1. Exercise 1: Create a 4×4 array of all sevens two different ways, once with np.full and once with np.ones times 7. Confirm they match with np.array_equal.
  2. Exercise 2: Use np.linspace to make 50 evenly spaced points from 0 to 2*pi, then print the first and last value to confirm both endpoints are included.
  3. Exercise 3: Seed a generator with np.random.default_rng(7), draw 20 integers from 1 to 100, then use boolean indexing to keep only the values above 50.

Frequently Asked Questions

How do I create a NumPy array?

The most common way to create a NumPy array is np.array() with a Python list or tuple, for example np.array([1, 2, 3]). For an array of a known shape filled with one value, use np.zeros(), np.ones(), or np.full(). For a number sequence, use np.arange() (by step) or np.linspace() (by count). All of these were tested on NumPy 2.4.6.

When should I use arange vs linspace?

Use arange when you know the step size (every 0.5, every 2). Use linspace when you know the number of points (for example 100 points from 0 to pi). For plotting, linspace is almost always what you want because it includes both endpoints and guarantees an exact count.

What does np.empty actually do?

np.empty allocates memory for the array without setting any values, so it holds whatever leftover numbers were already in that memory. Only use it when you plan to overwrite every element right away, since skipping the reset makes it slightly faster than np.zeros().

Why use default_rng instead of np.random.seed?

np.random.default_rng() is the modern NumPy random API, recommended since NumPy 1.17 and still current in NumPy 2.4.6 (at the time of writing). It produces higher quality random numbers and gives each generator its own state. The older np.random.seed() changes one shared global state, which can cause quiet bugs when different parts of your code reseed it.

Can I create a NumPy array from a CSV file directly?

Yes. np.loadtxt() and np.genfromtxt() read a CSV file straight into a NumPy array. For messier data with mixed types, headers, or missing values, Pandas read_csv() handles it better. Use NumPy for clean numeric files and Pandas for everything else.

What is the difference between np.array and np.asarray?

np.array() always builds a new copy. np.asarray() skips the copy when the input is already a NumPy array of the right dtype and returns the same object, which you can confirm with np.shares_memory(). Use asarray in functions that accept both lists and arrays so you avoid copying data you do not need to copy.

Interview Questions on NumPy Arrays

These come from real screens and onsites. Practice answering before you read each answer.

Q: Your data pipeline builds a buffer with np.empty(1000) and a teammate reports it “already contains zeros, so empty must be the same as zeros.” What do you tell them?

Tell them it is a coincidence they cannot rely on. np.empty() allocates memory without initializing it, so the values are whatever bytes happened to be in that memory block, and freshly requested pages from the OS often arrive zeroed. On a warm process that reuses memory, those same slots will hold leftover garbage, and code that assumed zeros will silently compute wrong results. Use np.zeros() unless you overwrite every element before reading any of them.

Q: After a refactor, code built on np.arange(0, 1, 0.1) sometimes crashes downstream with a length mismatch. What is the root cause and the fix?

The root cause is floating point rounding. 0.1 has no exact binary representation, so the accumulated step can land just under or just over the stop value, and the array ends up with one element more or fewer than expected. Any code that hard-codes the expected length then breaks. The fix is np.linspace(0, 0.9, 10) or similar, where you state the count explicitly, so the length is guaranteed no matter what the endpoints are.

Q: You are writing a library function that accepts either a Python list or a NumPy array. Should you convert the input with np.array or np.asarray, and why?

Use np.asarray(). If the caller passes a list, both functions build a new array, so there is no difference. But if the caller passes an ndarray that already has a compatible dtype, np.asarray() returns the same object with zero copying, while np.array() duplicates the whole buffer. On large arrays that is wasted memory and time on every call. The one caution: because asarray can return the caller’s own array, do not mutate it in place unless that is part of your function’s contract.

Q: Why does np.zeros(3, 4) raise an error while np.zeros((3, 4)) works?

The signature is np.zeros(shape, dtype). Called as np.zeros(3, 4), NumPy reads 3 as the shape and tries to interpret 4 as a dtype, which fails. A multi-dimensional shape must be passed as a single tuple, np.zeros((3, 4)), so it arrives as one argument. Only 1D arrays get the shortcut of a bare integer, since one number fully describes one dimension.

Q: What is the difference between np.random.seed(42) and np.random.default_rng(42), and which should new code use?

np.random.seed() sets one hidden global state shared by every legacy np.random call in the process, so any library or thread that also reseeds or draws from it changes your sequence behind your back. np.random.default_rng(42) returns an independent Generator object with its own state and a better underlying algorithm (PCG64). New code should create one or more Generator objects and pass them around explicitly; that gives reproducibility without global side effects, and it has been NumPy’s recommendation since version 1.17.

Q: You need to cut the memory of a large numeric array roughly in half before feeding it to a model. What array creation option helps, and what is the trade-off?

Pass an explicit dtype, for example np.array(data, dtype=np.float32) instead of the default float64, which halves memory at 4 bytes per element instead of 8. Most machine learning workloads train in float32 anyway, so this is standard practice. The trade-off is precision: float32 carries about 7 significant decimal digits versus roughly 15 for float64, so avoid it for accounting-style sums or algorithms that accumulate tiny differences over millions of operations.

Want more? NumPy official documentation documents everything this post could not fit.

Previous: NumPy: Introduction to Arrays, dtypes, and Why NumPy

Next: NumPy Indexing, Slicing, and Fancy Indexing

Series Home: Python + AI/ML Tutorial Series

RahulAuthor posts

Avatar for Rahul

Rahul is a passionate IT professional who loves to sharing his knowledge with others and inspiring them to expand their technical knowledge. Rahul's current objective is to write informative and easy-to-understand articles to help people avoid day-to-day technical issues altogether. Follow Rahul's blog to stay informed on the latest trends in IT and gain insights into how to tackle complex technical issues. Whether you're a beginner or an expert in the field, Rahul's articles are sure to leave you feeling inspired and informed.

No comment

Leave a Reply

Your email address will not be published. Required fields are marked *