NumPy Linspace: How to Create Evenly Spaced Arrays in Python
Updated on
Generating a sequence of evenly spaced numbers sounds trivial until you need exact control over the start, stop, and count. Python's built-in range() only handles integers, and even numpy.arange() can produce off-by-one surprises when floating-point step sizes accumulate rounding errors. If you have ever plotted a function and ended up with a jagged curve or missed the final x-value, you have run into exactly this problem. numpy.linspace() exists to solve it: you specify how many points you want between two endpoints, and NumPy calculates the exact spacing for you.
In this guide, you will learn the full np.linspace() API, see side-by-side comparisons with np.arange(), and walk through real-world use cases from plotting smooth curves to building multi-dimensional grids. Every code example is copy-paste ready and shows its output.
What Does np.linspace() Do?
numpy.linspace() returns an array of evenly spaced numbers over a specified interval. The key idea is that you define the number of samples rather than the step size.
Full Signature
numpy.linspace(start, stop, num=50, endpoint=True, retstep=False, dtype=None, axis=0)| Parameter | Type | Default | Description |
|---|---|---|---|
start | float or array-like | required | Start of the interval |
stop | float or array-like | required | End of the interval |
num | int | 50 | Number of samples to generate |
endpoint | bool | True | Whether to include stop in the output |
retstep | bool | False | If True, also return the step size |
dtype | dtype | None | Output array data type (inferred if None) |
axis | int | 0 | Axis along which to store samples (relevant when start/stop are arrays) |
Basic Example
import numpy as np
arr = np.linspace(0, 1, 5)
print(arr)
# Output: [0. 0.25 0.5 0.75 1. ]NumPy divides the interval [0, 1] into 4 equal gaps (one fewer than the 5 requested points) and returns all five boundary values, including both endpoints.
Controlling the Number of Points with num
The num parameter determines how many values are in the returned array. The default is 50, which is why calling np.linspace(0, 10) gives you 50 values even if you did not ask for them explicitly.
import numpy as np
# 3 points between 0 and 10
print(np.linspace(0, 10, num=3))
# Output: [ 0. 5. 10.]
# 6 points between -1 and 1
print(np.linspace(-1, 1, num=6))
# Output: [-1. -0.6 -0.2 0.2 0.6 1. ]A common mistake is to set num=1. In that case, only the start value is returned.
import numpy as np
print(np.linspace(0, 10, num=1))
# Output: [0.]The endpoint Parameter: Including or Excluding the Stop Value
By default, endpoint=True, which means the stop value is the last element of the array. When you set endpoint=False, the stop value is excluded and the spacing changes.
import numpy as np
with_endpoint = np.linspace(0, 1, 5, endpoint=True)
print("endpoint=True: ", with_endpoint)
# Output: endpoint=True: [0. 0.25 0.5 0.75 1. ]
without_endpoint = np.linspace(0, 1, 5, endpoint=False)
print("endpoint=False:", without_endpoint)
# Output: endpoint=False: [0. 0.2 0.4 0.6 0.8]| Setting | Formula for step | Last value |
|---|---|---|
endpoint=True | (stop - start) / (num - 1) | Exactly stop |
endpoint=False | (stop - start) / num | One step before stop |
When is endpoint=False useful? A classic example is generating angles for a discrete Fourier transform, where you want N points in [0, 2*pi) without repeating the starting angle.
import numpy as np
N = 8
angles = np.linspace(0, 2 * np.pi, N, endpoint=False)
print(np.round(angles, 4))
# Output: [0. 0.7854 1.5708 2.3562 3.1416 3.927 4.7124 5.4978]Getting the Step Size with retstep
When retstep=True, np.linspace() returns a tuple: the array and the computed step size. This is handy when you need the spacing for later calculations without computing it yourself.
import numpy as np
values, step = np.linspace(0, 10, num=5, retstep=True)
print("Values:", values)
print("Step: ", step)
# Output:
# Values: [ 0. 2.5 5. 7.5 10. ]
# Step: 2.5You can use the step size for numerical differentiation, integration, or simply to verify that your grid has the resolution you expect.
import numpy as np
x, dx = np.linspace(0, np.pi, 1000, retstep=True)
# Approximate integral of sin(x) from 0 to pi using the trapezoidal rule
integral = np.trapz(np.sin(x), dx=dx)
print(f"Integral of sin(x) over [0, pi]: {integral:.6f}")
# Output: Integral of sin(x) over [0, pi]: 2.000000np.linspace() vs np.arange(): When to Use Which
Both functions create sequences of numbers, but they differ in a fundamental way: linspace lets you specify the count, while arange lets you specify the step size.
| Feature | np.linspace() | np.arange() |
|---|---|---|
| You specify | Number of points (num) | Step size (step) |
| Endpoint included? | Yes by default | Never (exclusive stop) |
| Float step precision | Exact (divides interval) | Can accumulate rounding error |
| Typical use | Plotting, interpolation | Integer ranges, iteration |
| Returns step size? | Yes, via retstep=True | No (you already know it) |
Floating-point pitfall with arange
import numpy as np
# arange can produce unexpected element counts with float steps
arr = np.arange(0, 1, 0.1)
print(len(arr), arr)
# Output: 10 [0. 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9]
# Slightly different bounds can cause an extra element
arr2 = np.arange(0, 1.0 + 1e-15, 0.1)
print(len(arr2))
# Output: 11With linspace, you always get exactly the number of elements you asked for. When working with floating-point intervals, prefer linspace unless you genuinely need a specific step size and integer-friendly bounds.
Practical Use Cases
1. Plotting Smooth Curves
The most common use of np.linspace() is generating x-values for plotting mathematical functions. More points produce smoother curves.
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(-2 * np.pi, 2 * np.pi, 300)
y = np.sin(x) / x # sinc-like function (warning: division by zero at x=0)
# Handle division by zero
y = np.where(np.isclose(x, 0), 1.0, np.sin(x) / x)
plt.figure(figsize=(8, 4))
plt.plot(x, y, linewidth=1.5)
plt.title("sinc-like function: sin(x)/x")
plt.xlabel("x")
plt.ylabel("y")
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig("sinc_plot.png", dpi=100)
plt.show()2. Signal Processing: Generating Time Axes
When simulating or analyzing signals, you need a time vector with a known sampling rate. linspace makes this straightforward.
import numpy as np
duration = 1.0 # seconds
sample_rate = 44100 # Hz (CD quality)
num_samples = int(duration * sample_rate)
t = np.linspace(0, duration, num_samples, endpoint=False)
# Generate a 440 Hz sine wave (concert A)
frequency = 440
signal = np.sin(2 * np.pi * frequency * t)
print(f"Time array shape: {t.shape}")
print(f"First 5 time values: {t[:5]}")
print(f"First 5 signal values: {np.round(signal[:5], 6)}")
# Output:
# Time array shape: (44100,)
# First 5 time values: [0.00000000e+00 2.26757370e-05 4.53514739e-05 6.80272109e-05 9.07029478e-05]
# First 5 signal values: [0. 0.062648 0.125178 0.187474 0.24942 ]Notice that endpoint=False is used here because the signal is periodic: the sample at t = 1.0 would duplicate the sample at t = 0.0.
3. Creating Color Gradients
linspace is useful for interpolating between color values when building custom color maps.
import numpy as np
# Interpolate between blue (0, 0, 255) and red (255, 0, 0) in 5 steps
steps = 5
r = np.linspace(0, 255, steps).astype(int)
g = np.zeros(steps, dtype=int)
b = np.linspace(255, 0, steps).astype(int)
colors = np.column_stack([r, g, b])
for i, c in enumerate(colors):
print(f"Step {i}: RGB({c[0]:3d}, {c[1]:3d}, {c[2]:3d})")
# Output:
# Step 0: RGB( 0, 0, 255)
# Step 1: RGB( 63, 0, 191)
# Step 2: RGB(127, 0, 127)
# Step 3: RGB(191, 0, 63)
# Step 4: RGB(255, 0, 0)Multi-Dimensional Grids with linspace and meshgrid
For 2D or 3D computations (heat maps, surface plots, contour plots), combine np.linspace() with np.meshgrid().
import numpy as np
x = np.linspace(-3, 3, 100)
y = np.linspace(-3, 3, 100)
X, Y = np.meshgrid(x, y)
# Compute a 2D Gaussian
Z = np.exp(-(X**2 + Y**2))
print(f"X shape: {X.shape}")
print(f"Z min: {Z.min():.6f}, Z max: {Z.max():.6f}")
# Output:
# X shape: (100, 100)
# Z min: 0.000001, Z max: 1.000000import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(-3, 3, 200)
y = np.linspace(-3, 3, 200)
X, Y = np.meshgrid(x, y)
Z = np.exp(-(X**2 + Y**2))
plt.figure(figsize=(6, 5))
plt.contourf(X, Y, Z, levels=20, cmap="viridis")
plt.colorbar(label="Amplitude")
plt.title("2D Gaussian via linspace + meshgrid")
plt.xlabel("x")
plt.ylabel("y")
plt.tight_layout()
plt.savefig("gaussian_contour.png", dpi=100)
plt.show()This pattern scales to any dimensionality. For 3D grids, add a third linspace call and use np.meshgrid(x, y, z).
The dtype Parameter
By default, np.linspace() returns float64 values. You can override this with the dtype parameter, though be aware that integer types will truncate the computed values.
import numpy as np
# Float64 (default)
print(np.linspace(0, 5, 6).dtype)
# Output: float64
# Explicit float32 for memory savings
arr32 = np.linspace(0, 1, 5, dtype=np.float32)
print(arr32, arr32.dtype)
# Output: [0. 0.25 0.5 0.75 1. ] float32
# Integer dtype truncates values
arr_int = np.linspace(0, 10, 5, dtype=int)
print(arr_int)
# Output: [ 0 2 5 7 10]When using integer dtypes, the fractional parts are simply dropped (floor behavior), which can produce unevenly spaced results. Stick with float types unless you have a specific reason to convert.
Common Patterns: Generating X-Values for Function Plotting
Here is a concise reference for patterns you will use repeatedly.
Pattern 1: Standard function plot
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(0, 10, 500)
plt.plot(x, np.sin(x), label="sin(x)")
plt.plot(x, np.cos(x), label="cos(x)")
plt.legend()
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()Pattern 2: Parametric curve
import numpy as np
import matplotlib.pyplot as plt
t = np.linspace(0, 2 * np.pi, 1000)
x = 16 * np.sin(t)**3
y = 13 * np.cos(t) - 5 * np.cos(2*t) - 2 * np.cos(3*t) - np.cos(4*t)
plt.figure(figsize=(5, 5))
plt.plot(x, y, color="red")
plt.title("Heart Curve")
plt.axis("equal")
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()Pattern 3: Evaluating a polynomial over a range
import numpy as np
coefficients = [1, -3, 2] # x^2 - 3x + 2
x = np.linspace(-1, 4, 6)
y = np.polyval(coefficients, x)
for xi, yi in zip(x, y):
print(f"x={xi:5.2f} -> y={yi:5.2f}")
# Output:
# x=-1.00 -> y= 6.00
# x= 0.00 -> y= 2.00
# x= 1.00 -> y= 0.00
# x= 2.00 -> y= 0.00
# x= 3.00 -> y= 2.00
# x= 4.00 -> y= 6.00Visualizing Linspace-Generated Data with PyGWalker
When working with arrays created by np.linspace(), you often need to explore the resulting data interactively -- adjusting axes, filtering ranges, or comparing multiple series without rewriting plotting code each time. PyGWalker (opens in a new tab) is an open-source Python library that turns any Pandas DataFrame into a Tableau-like interactive visualization interface directly inside Jupyter Notebook.
Here is how to combine linspace-generated data with PyGWalker for interactive exploration:
import numpy as np
import pandas as pd
import pygwalker as pyg
# Generate data using linspace
x = np.linspace(0, 4 * np.pi, 200)
df = pd.DataFrame({
"x": x,
"sin": np.sin(x),
"cos": np.cos(x),
"damped_sin": np.exp(-0.1 * x) * np.sin(x),
})
# Launch interactive visualization in Jupyter
walker = pyg.walk(df)With PyGWalker open, you can drag x to the x-axis, drop sin and damped_sin onto the y-axis, and instantly compare waveforms. You can switch between line charts, scatter plots, and bar charts without writing additional code. This is especially useful when you are generating data across different parameter ranges with linspace and want to visually confirm behavior before committing to a final plot.
Frequently Asked Questions
What is the difference between np.linspace and np.arange?
np.linspace() takes a start value, stop value, and the number of points you want. np.arange() takes a start value, stop value, and a step size. Use linspace when you care about how many points you get (plotting, interpolation). Use arange when you care about the exact step between values (integer sequences, loops).
Does np.linspace include the endpoint by default?
Yes. By default, endpoint=True, which means the stop value is included as the last element. Set endpoint=False to exclude it, which is useful for periodic signals or Fourier transforms.
How do I get the step size from np.linspace?
Pass retstep=True to get a tuple of (array, step_size). For example: values, step = np.linspace(0, 10, 100, retstep=True).
Can np.linspace generate integers?
You can pass dtype=int, but the values will be truncated (not rounded), which often produces uneven spacing. If you need evenly spaced integers, np.arange() is usually a better choice.
How many points should I use for a smooth plot?
For most 2D function plots, 200 to 500 points produce visually smooth curves. For high-frequency functions or publication-quality figures, 1000 points is a safe choice. More points increase memory usage and rendering time, so balance smoothness against performance.
Conclusion
numpy.linspace() is one of the most frequently used NumPy functions for a reason: it gives you precise control over how many evenly spaced values you generate between two endpoints. Unlike np.arange(), it never surprises you with an extra or missing element due to floating-point rounding. The endpoint, retstep, and dtype parameters give you fine-grained control for everything from quick plots to signal processing pipelines.
Key takeaways:
- Use
numto set the exact number of output values. - Set
endpoint=Falsefor periodic data where the stop value would duplicate the start. - Use
retstep=Truewhenever you need the step size for downstream computation. - Prefer
linspaceoverarangefor floating-point intervals. - Combine
linspacewithmeshgridfor multi-dimensional grids.
With these patterns in your toolkit, you can confidently generate numerical sequences for plotting, simulation, interpolation, and scientific computing in Python.