Stop writing loops
The deepest habit you bring from non-array languages is writing element-wise operations as loops: for i in range(len(x)): result[i] = x[i] * 2. NumPy lets you say result = x * 2 and produces the same answer, faster, with less code, and (this is the underrated part) with less room for off-by-one errors.
The harder shift is two-array operations. With Python lists you'd zip(a, b) and write a comprehension. With NumPy you write a * b — and as long as the shapes are compatible, NumPy figures out the rest.
Broadcasting in one paragraph
Broadcasting is NumPy's set of rules for combining arrays of different shapes. The rules: align trailing dimensions; a dimension of size 1 stretches to match; missing dimensions are treated as size 1. The reason you care: it lets you say "add this row vector to every row of this matrix" or "subtract this scalar from every element" without writing a single loop, and without allocating intermediate copies.