NumPy linspace: The Formula, the Endpoint and vs arange

np.linspace returns evenly spaced numbers between two endpoints, and you tell it how many you want rather than how big the gaps are:

np.linspace(start, stop, num=50)

np.linspace(0, 10, 5)      # [ 0.   2.5  5.   7.5 10. ]
                           #                        ^ stop is INCLUDED

That inclusive stop is the difference from np.arange, which leaves it out. It’s also why linspace is the safer of the two.

Output below is from real runs on Python 3.12.5, NumPy 2.5.3.

Using np.linspace in Python

Three arguments cover almost every use. The third one is a count, which is the part people get wrong:

import numpy as np

print(np.linspace(0, 10, 5))          # 5 values, and 10 IS included
print(np.linspace(0, 1, 11))          # 11 values, step 0.1

# the count is the third argument, not the step
print("\ndefault number of values:", np.linspace(0, 1).size)

Output:

[ 0.   2.5  5.   7.5 10. ]
[0.  0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1. ]

default number of values: 50
Command Prompt showing numpy linspace producing five and eleven evenly spaced values
Leave num out and you get 50 values, not 50 steps.

np.linspace(0, 1, 11) gives eleven values because the count includes both ends. Ask for 10 and the spacing becomes 0.111 instead of 0.1.

A useful habit: the number of gaps is always one less than the number of values.

The np.linspace formula

The spacing is fixed by the arguments, and retstep=True hands it back so you don’t have to work it out:

import numpy as np

start, stop, num = 0, 10, 5

values, step = np.linspace(start, stop, num, retstep=True)
print("values:", values)
print("step from retstep:", step)

# that is exactly what the formula gives
print("\n(stop - start) / (num - 1) =", (stop - start) / (num - 1))

# with endpoint=False the divisor changes
values2, step2 = np.linspace(start, stop, num, endpoint=False, retstep=True)
print("\nendpoint=False values:", values2)
print("step:", step2, "which is (stop - start) / num =", (stop - start) / num)

Output:

values: [ 0.   2.5  5.   7.5 10. ]
step from retstep: 2.5

(stop - start) / (num - 1) = 2.5

endpoint=False values: [0. 2. 4. 6. 8.]
step: 2.0 which is (stop - start) / num = 2.0
Command Prompt showing the numpy linspace step returned by retstep matching the manual formula
retstep confirms the formula, with and without the endpoint.
SettingFormula for the step
endpoint=True (default)(stop - start) / (num - 1)
endpoint=False(stop - start) / num

The divisor changes because dropping the endpoint removes one value but keeps the same span, so each gap has to be slightly larger.

np.linspace vs np.arange

They answer two different questions. linspace answers “how many values”, arange answers “how big a step”:

import numpy as np

# linspace: "give me N values" -- the stop is included
print("linspace(0, 10, 5) :", np.linspace(0, 10, 5))

# arange: "step by this much" -- the stop is excluded
print("arange(0, 10, 2.5) :", np.arange(0, 10, 2.5))

print()
print("linspace dtype:", np.linspace(0, 10, 5).dtype)
print("arange  dtype:", np.arange(0, 10, 2).dtype, "<- integers stay integers")

Output:

linspace(0, 10, 5) : [ 0.   2.5  5.   7.5 10. ]
arange(0, 10, 2.5) : [0.  2.5 5.  7.5]

linspace dtype: float64
arange  dtype: int64 <- integers stay integers
np.linspacenp.arange
You specifyThe number of valuesThe step size
Stop valueIncludedExcluded
Default dtypeAlways floatFollows the inputs
Safe with floatsYesNo, see below
Best forPlot axes, interpolationInteger counters, indices

For whole-number sequences arange is the natural choice and keeps the integer dtype. There is more on it in the arange function guide.

Why is np.arange unreliable with float steps?

This is the reason to prefer linspace, and it is stranger than the usual explanation suggests:

import numpy as np

# Same step. Same width of interval. Different number of elements.
a = np.arange(0, 0.3, 0.1)
b = np.arange(1, 1.3, 0.1)

print("arange(0, 0.3, 0.1) ->", a, "  length", len(a))
print("arange(1, 1.3, 0.1) ->", b, "length", len(b), "<- 1.3 should NOT be here")

print()
print("arange computes ceil((stop - start) / step) and rounding decides the count:")
print("  (0.3 - 0) / 0.1   =", repr((0.3 - 0) / 0.1))
print("  (1.3 - 1) / 0.1   =", repr((1.3 - 1) / 0.1))

print()
print("linspace cannot do this, because you state the count yourself:")
print("  linspace(1, 1.3, 3) ->", np.linspace(1, 1.3, 3))

Output:

arange(0, 0.3, 0.1) -> [0.  0.1 0.2]   length 3
arange(1, 1.3, 0.1) -> [1.  1.1 1.2 1.3] length 4 <- 1.3 should NOT be here

arange computes ceil((stop - start) / step) and rounding decides the count:
  (0.3 - 0) / 0.1   = 2.9999999999999996
  (1.3 - 1) / 0.1   = 3.0000000000000004

linspace cannot do this, because you state the count yourself:
  linspace(1, 1.3, 3) -> [1.   1.15 1.3 ]
Command Prompt showing numpy arange returning four elements including the stop value for one range and three for another
Same step, same span, different results.

np.arange(0, 0.3, 0.1) behaves. np.arange(1, 1.3, 0.1) returns four values and includes 1.3, which it was supposed to exclude.

The cause is in the printed division. (1.3 - 1) / 0.1 does not come out as exactly 3 in binary floating point, so the rounding lands the wrong side of the boundary.

linspace cannot make this mistake. You state the count, so the count is what you get.

np.linspace endpoints and edge cases

Both ends are exact, which matters when the result feeds an interpolation or an axis limit:

import numpy as np

# linspace hits both ends exactly, which matters for plotting and for interpolation
grid = np.linspace(0, 1, 11)
print("first:", repr(grid[0]))
print("last :", repr(grid[-1]))
print("last is exactly 1.0?", grid[-1] == 1.0)

print()
# edge cases worth knowing
print("num=1 ->", np.linspace(0, 10, 1), "(just the start)")
print("num=0 ->", np.linspace(0, 10, 0), "(empty)")
print("backwards ->", np.linspace(10, 0, 5))

Output:

first: np.float64(0.0)
last : np.float64(1.0)
last is exactly 1.0? True

num=1 -> [0.] (just the start)
num=0 -> [] (empty)
backwards -> [10.   7.5  5.   2.5  0. ]
  • num=1 returns just the start value, not the stop.
  • num=0 returns an empty array rather than raising.
  • A start larger than the stop counts downward, no special argument needed.
  • A negative num raises ValueError.

The exact endpoint is why linspace suits axis work. If you’re setting an axis range, a grid that stops at 0.9999999 will quietly clip your last point.

Setting the linspace dtype

linspace always computes in floating point, then casts if you ask it to:

import numpy as np

print("default      :", np.linspace(0, 10, 5).dtype)
print("dtype=int    :", np.linspace(0, 10, 5, dtype=int), np.linspace(0, 10, 5, dtype=int).dtype)
print("dtype=float32:", np.linspace(0, 1, 5, dtype=np.float32).dtype)

print()
# integer casting TRUNCATES, it does not round
print("float values :", np.linspace(0, 10, 4))
print("as int       :", np.linspace(0, 10, 4, dtype=int), "<- 3.33 became 3, 6.67 became 6")

Output:

default      : float64
dtype=int    : [ 0  2  5  7 10] int64
dtype=float32: float32

float values : [ 0.          3.33333333  6.66666667 10.        ]
as int       : [ 0  3  6 10] <- 3.33 became 3, 6.67 became 6
Command Prompt showing numpy linspace with an integer dtype truncating values rather than rounding them
dtype=int truncates: 3.33 becomes 3 and 6.67 becomes 6.

That truncation catches people out. np.linspace(0, 10, 4, dtype=int) gives [0, 3, 6, 10], where the gaps are no longer even.

If you want evenly spaced integers, use np.arange with an integer step, or round explicitly with np.round before casting.

Using linspace with Matplotlib

Generating a smooth x axis is the single most common use of the function:

import numpy as np
import matplotlib.pyplot as plt

# this is what linspace is for: a smooth x axis
x_coarse = np.linspace(0, 2 * np.pi, 8)
x_smooth = np.linspace(0, 2 * np.pi, 300)

plt.figure(figsize=(8, 4.5))
plt.plot(x_coarse, np.sin(x_coarse), "o-", color="#c0392b", label="num=8")
plt.plot(x_smooth, np.sin(x_smooth), color="#0b6bcb", lw=2, label="num=300")
plt.title("linspace controls how smooth a curve looks")
plt.xlabel("x"); plt.ylabel("sin(x)")
plt.legend(); plt.grid(alpha=.3); plt.tight_layout()
plt.show()

print("8 points  :", np.round(x_coarse, 3))
print("300 points: first three", np.round(x_smooth[:3], 4), "...")

Output:

8 points  : [0.    0.898 1.795 2.693 3.59  4.488 5.386 6.283]
300 points: first three [0.    0.021 0.042] ...
Matplotlib plot comparing a sine curve drawn with 8 linspace points against one drawn with 300
Eight points make a sine wave look like a polygon. 300 make it a curve.

The rule of thumb is a few hundred points for a smooth line. Beyond about a thousand you’re adding file size, not detail.

Use the same linspace array for both x and the function of x, so they can never fall out of step. The same applies when you draw a best fit line over scattered data.

Two-dimensional linspace with the axis argument

Pass arrays as start and stop and you get one sequence per pair:

import numpy as np

# start and stop can be arrays, giving one row or column per pair
starts = np.array([0, 100])
stops = np.array([1, 200])

print("axis=0 (default):")
print(np.linspace(starts, stops, 5))

print("\naxis=1:")
print(np.linspace(starts, stops, 5, axis=1))

Output:

axis=0 (default):
[[  0.   100.  ]
 [  0.25 125.  ]
 [  0.5  150.  ]
 [  0.75 175.  ]
 [  1.   200.  ]]

axis=1:
[[  0.     0.25   0.5    0.75   1.  ]
 [100.   125.   150.   175.   200.  ]]

axis=0 stacks the sequences as rows, axis=1 as columns. This builds a coordinate grid in one call, with no reshaping.

Common np.linspace mistakes

SymptomCauseFix
One more value than expectednum counts values, not gapsAsk for n, get n
Got 50 valuesnum was left outAlways pass it
Spacing is not what you wantedConfused count with stepUse retstep=True to check
Integers are unevenly spaceddtype=int truncatesRound before casting
Stop value is missingYou used arangeUse linspace

If you work with NumPy ranges a lot, these go well with this page:

Frequently asked questions

What does np.linspace do?

It returns evenly spaced numbers over an interval, with the stop value included by default. Full details are in the numpy.linspace reference.

What is the linspace formula?

The step is (stop - start) / (num - 1) by default. With endpoint=False it becomes (stop - start) / num.

What is the difference between linspace and arange?

linspace takes the number of values and includes the stop. arange takes a step size and excludes the stop.

Does np.linspace include the endpoint?

Yes, unless you pass endpoint=False. That is the main practical difference from arange.

How many values does np.linspace return by default?

50, because num defaults to 50. It is easy to miss, since the argument is usually supplied.

How do I get the step size from linspace?

Pass retstep=True and it returns a tuple of the array and the step.

Why is np.arange giving me an extra element?

Floating-point division decides the element count, and it can round the wrong way. np.arange(1, 1.3, 0.1) returns four values including 1.3. Use linspace for float ranges.