Python runs one instruction at a time and checks the type of every value as it goes.
That flexibility is costly when the same small sum is repeated millions of times.
Vectorisation moves the loop out of Python and into precompiled code written in C.
Inside that code the type is known once for the whole array, so no check is needed for each element.
Modern processors can also apply one instruction to several numbers at once, which NumPy uses where it can.
Arithmetic, comparisons and maths functions such as np.sqrt all work element by element on whole arrays.
Comparisons return arrays of True and False, which can then select values, as in prices[prices > 100].
Conditional logic uses np.where, for example to apply a discount only where a quantity is above 10.
Sums, means and maximums along a whole dimension are vectorised too, so a loop is rarely needed.
The gain over a plain Python loop is often tens of times or more.
Each vectorised step creates a temporary array, so a long chain of them can use a lot of memory.
Logic where each step depends on the previous result is harder to vectorise.
Simple cases such as running totals are covered by functions like cumsum, but a rule applied day by day may need a loop.
Column maths in pandas is vectorised the same way, because it runs on NumPy underneath.
The usual mistake is writing a loop out of habit, or calling apply with a Python function, which brings the slow loop back.