Every problem with np.max in NumPy and how to fix it

You already know np.max returns the largest value in an array. What trips people up is everything around that one fact: which axis it reduces, what happens when a NaN sneaks in, why the shape breaks your next calculation and when to reach for amax, maximum or fmax instead.

Syntax and parameters

numpy.max(a, axis=None, out=None, keepdims=<no value>, initial=<no value>, where=<no value>)
  • a: the input array or anything array-like (a list, a tuple, a nested list)
  • axis: an int, a tuple of ints or None. Controls which dimension gets reduced. None flattens the array first
  • out: an existing array to write the result into instead of allocating a new one
  • keepdims: when True, keeps the reduced axes in the output as size-1 dimensions instead of dropping them
  • initial: a starting value for the comparison, required if you want to call max on an array that might be empty
  • where: a boolean array or condition that limits which values get compared

My array has 40 numbers and I only want the biggest one

This is the case everyone starts with. Call np.max on the array with no axis argument and it flattens the array internally before comparing every element.

import numpy as np

readings = np.array([12, 45, 3, 78, 22, 91, 8])
print(np.max(readings))
# 91

The same call works on a 2D or 3D array too, since axis defaults to None. A 3×4 matrix of sensor readings collapses to one scalar the same way a flat list does. This is the version people usually mean when they just say “numpy max”, no axis, no conditions, just the single biggest number in the whole thing.

Should I write np.max(arr) or arr.max()

Both exist and both return the same result, since arr.max() is just the ndarray method version of the same reduction.

import numpy as np

readings = np.array([12, 45, 3, 78, 22, 91, 8])

print(np.max(readings))
# 91
print(readings.max())
# 91

Use whichever reads better in context. The method form (readings.max()) chains naturally onto an existing array without wrapping it in another set of parentheses, which is why you will see it often in the middle of longer expressions. The function form (np.max(readings)) is the only option once your input might not already be an ndarray, for example a plain Python list, since a list has no .max() method of its own.

plain_list = [12, 45, 3, 78]
print(np.max(plain_list))
# 78, works fine
# plain_list.max() would raise AttributeError

My 2D array gives me one number, but I need one per row or column

Pass an axis and np.max stops flattening. It runs the comparison along just that dimension and returns an array instead of a scalar.

import numpy as np

scores = np.array([
    [63, 72, 75, 51],
    [44, 53, 90, 56],
    [71, 77, 82, 91],
])

print(np.max(scores, axis=0))  # column-wise: [71 77 90 91]
print(np.max(scores, axis=1))  # row-wise: [75 90 91]

Axis 0 moves down each column, so you get the largest value in each column position. Axis 1 moves across each row, so you get the largest value per row. This is the single most common source of confusion with np.max, since the direction feels backward the first few times. A trick that helps: axis 0 removes the row dimension, so what is left is one value per column.

You can also pass a tuple of axes on a 3D or higher array, which reduces over both dimensions at once and leaves a single axis remaining.

cube = np.arange(24).reshape(2, 3, 4)
print(np.max(cube, axis=(0, 1)))
# [20 21 22 23]

np.max returns nan even though most of my data is valid

NumPy propagates NaN values on purpose. If even one element in the array is NaN, the max of the whole array (or that axis) becomes NaN, since NumPy assumes you want to know when your data has a gap rather than silently hiding it.

import numpy as np

week_temps = np.array([21.4, 22.1, np.nan, 23.0, 20.8])
print(np.max(week_temps))
# nan

When you want the maximum among the valid readings instead, switch to np.nanmax, which skips NaN entirely.

print(np.nanmax(week_temps))
# 23.0

Keep the two straight in your head: np.max tells you when something is missing, np.nanmax gets you a usable answer despite that. Reach for nanmax only after you have decided on purpose that skipping the gap is fine for your use case, not as a reflex whenever np.max surprises you.

My reduced array does not broadcast back against the original anymore

If you take the max along an axis, that axis disappears from the shape by default. That is fine on its own, but it breaks the moment you try to subtract or divide the reduced array against the original array, since the shapes no longer line up. Set keepdims=True and the reduced axis stays in the output as a dimension of size 1, which broadcasting rules can work with directly.

import numpy as np

grid = np.array([[10, 25, 17], [15, 11, 22]])

row_max = np.max(grid, axis=1)
print(row_max.shape)
# (2,)

row_max_kept = np.max(grid, axis=1, keepdims=True)
print(row_max_kept.shape)
# (2, 1)

normalized = grid / row_max_kept
print(normalized)

Without keepdims, grid / row_max raises a broadcasting error on a 2D array, because a shape of (2,) does not line up with (2, 3) on the trailing axis. With keepdims, the shape (2, 1) broadcasts across each row cleanly. This is one of the few places where reaching for reshaping an array manually would work too, but keepdims does it in one argument instead of a separate call.

Recent NumPy versions changed the default for keepdims from a plain False to a special “no value” sentinel, which matters only if you subclass ndarray. For plain arrays the behavior is unchanged, you still need to pass keepdims=True explicitly to get it.

I need the position of the maximum, not the value itself

np.max only ever returns values. The moment your actual question is “which row had the best score” or “at what index did this spike happen,” you need np.argmax instead, and often both functions side by side.

import numpy as np

sensor_log = np.array([18, 22, 19, 31, 27, 20])

print(np.max(sensor_log))     # 31, the peak value
print(np.argmax(sensor_log))  # 3, the index where it happened

On a 2D array, argmax also takes an axis argument and returns one index per row or column, matching the shape you would expect from np.max with the same axis.

grid = np.array([[5, 9, 2], [8, 1, 12]])
print(np.argmax(grid, axis=1))
# [1 2]

If more than one element ties for the maximum, argmax always returns the index of the first occurrence, not all of them. If you need every tied index, compare the array against its own max and use np.where on the result.

Calling np.max on an empty array raises a ValueError

An array with nothing in it has no maximum by definition, and NumPy refuses to guess one.

import numpy as np

empty = np.array([])
print(np.max(empty))
# ValueError: zero-size array to reduction operation maximum which has no identity

The initial parameter gives np.max a fallback value to compare against, which also covers the empty case.

print(np.max(empty, initial=0))
# 0.0

This comes up constantly in loops that build up results conditionally, where some iteration might end up with nothing collected before you call max on it. Passing initial up front is cheaper than wrapping every call in a try or except block. Note that initial behaves differently from the default argument in Python’s built-in max function, since NumPy always includes initial as one of the candidates for the comparison rather than only using it for a genuinely empty input.

print(np.max([5], initial=6))
# 6, because 6 is treated as a real candidate value
print(max([5], default=6))
# 5, Python's own max only falls back to default when the iterable is empty

I only want the max among values that meet a condition

The where parameter filters which values actually take part in the comparison, similar to how you would first filter a pandas dataframe before running a calculation on it. Any position marked False in the mask gets skipped entirely.

import numpy as np

arr = np.array([12, 25, 32, 47, 50, 36])

result = np.max(arr, initial=0, where=(arr < 40))
print(result)
# 36

Notice initial is required alongside where. Without it, NumPy has no starting value to fall back on if nothing in the array satisfies the condition and it raises the same error as an empty array would.

np.max(arr, where=(arr < 40))
# ValueError: reduction operation 'maximum' does not have an identity,
# so to use a where mask one has to specify 'initial'

A good default is to set initial lower than any value you expect the array to contain, so it never accidentally wins the comparison.

Which one do I actually need: max, amax, maximum or fmax

These four names get mixed up constantly because three of them do genuinely different jobs, and one is just an alias.

  • np.max(a): the maximum inside a single array, optionally along an axis
  • np.amax(a): an older alias for np.max, kept for backward compatibility. The two behave the same way in every case
  • np.maximum(a, b): compares two arrays element by element and returns an array of the larger value at each position. It does not reduce anything
  • np.fmax(a, b): the same element-wise comparison as maximum, but ignores NaN instead of propagating it
import numpy as np

a = np.array([3, 7, 2])
b = np.array([5, 1, 9])

print(np.maximum(a, b))
# [5 7 9]

The rule that clears up most of the confusion: max and amax always reduce one array down to fewer values. maximum and fmax always compare two arrays and keep the same shape. If your two inputs are the exact same array, calling maximum(a, a) is a wasteful way of writing a plain copy, since it never actually reduces anything.

I need to compare two arrays and keep the higher value at each position

This is the case np.maximum exists for, and it comes up in real data pulls whenever you have parallel results that need merging.

import numpy as np

week_1 = np.array([21.4, 22.1, 19.8, 23.0])
week_2 = np.array([20.9, 23.5, 18.2, 24.1])

best_of_both = np.maximum(week_1, week_2)
print(best_of_both)
# [21.4 23.5 19.8 24.1]

If either input contains NaN, maximum propagates it at that position, matching the same caution np.max applies to a single array. Swap in np.fmax if you want NaN values ignored in favor of whatever real number is available.

week_2_gappy = np.array([20.9, np.nan, 18.2, 24.1])
print(np.maximum(week_1, week_2_gappy))
# [21.4  nan 19.8 24.1]
print(np.fmax(week_1, week_2_gappy))
# [21.4 22.1 19.8 24.1]

Broadcasting applies here as well, so comparing a full array against a single scalar works without reshaping anything.

print(np.maximum(week_1, 22.0))
# [22.  22.1 22.  23. ]

My program calls max thousands of times and memory keeps climbing

Every call to np.max allocates a new output array by default, which adds up fast inside a loop that runs across a large dataset. The out parameter lets you reuse the same block of memory across repeated calls instead of allocating fresh each time.

import numpy as np

buffer = np.empty(4, dtype=np.float32)
data = np.array([[10.5, 20.1, 30.7, 15.2], [12.3, 25.6, 18.9, 22.4]])

np.max(data, axis=0, out=buffer)
print(buffer)
# [12.3 25.6 30.7 22.4]

The out array must already match the shape and dtype NumPy expects from the reduction, otherwise the call raises an error rather than resizing it for you. This matters most in tight loops processing thousands of small arrays, where repeated allocation becomes the actual bottleneck rather than the comparison itself.

Aditya Gupta
Aditya Gupta

Aditya Gupta is a founding member and editor at CodeForGeek. He first found his way into tech by reading articles, and now writes approachable guides to Node.js security, authentication, AI tools, coding agents, and web scraping.

Articles: 529