MATLAB & GNU Octave Module 3 – Array calculations and vectorised thinking
Matrix vs element-wise operators and broadcasting
Choose between * and .*, / and ./, ^ and .^ in Octave and MATLAB, predict result sizes under implicit expansion and catch accidental N-by-N results.
What you will learn
- Choose between *, .*, /, ./, \, ^ and .^ for the calculation you mean
- Predict result sizes under implicit expansion, also called broadcasting
- Detect accidental N-by-N results from mixing row and column vectors
- Compute dot products, tables of values and centred data without loops
Before you start
What you’ll learn · Before you start
What you will learn
- Choose between *, .*, /, ./, \, ^ and .^ for the calculation you mean
- Predict result sizes under implicit expansion, also called broadcasting
- Detect accidental N-by-N results from mixing row and column vectors
- Compute dot products, tables of values and centred data without loops
Before you start
On this page
[1 2 3] * [4 5 6] stops with an error, [1 2 3] .* [4 5 6] gives 4 10 18, and [1 2 3] * [4 5 6]' gives 32. One
dot, or one quote, changes the meaning of a line, and the most dangerous mix-up raises no error at all: a row minus a
column of 100 values is a 100-by-100 matrix.
Here you choose between the matrix operators and their element-wise twins, predict the size of every result under implicit expansion, and catch the row-and-column mix-up before it reaches a plot or a report.
Matrix products combine rows and columns; element-wise products pair positions
MATLAB and Octave have two families of arithmetic operators. Without a dot, *, / and ^ follow the rules of
linear algebra; with a dot, .*, ./ and .^ work position by position, as an
element-wise operation: An operation done separately at each position of its arrays, such as .*, ./, .^, + and -. [1 2 3] .* [4 5 6] gives 4 10 18; no products are added up.. MathWorks writes that “the period character (.)
distinguishes the array operations from the matrix operations”. Step through one example of each:
The drawing shows the whole scene; its 5 steps are listed below.
Every step as text
- Two inputs. A is 2-by-2 and b is 2-by-1. The matrix product needs the columns of the left factor to match the rows of the right one, 2 and 2 here, and gives a 2-by-1 result.
- Row 1 of A times b. Each element of A * b is a row of A times the column b, added up. Row 1 gives 1 × 5 + 2 × 6 = 17.
- Row 2 of A times b. Row 2 gives 3 × 5 + 4 × 6 = 39, so A * b is the column [17; 39], as the session printed.
- A .* b pairs positions. .* multiplies elements in the same position and adds nothing up. b has one column, so it is used for both columns of A, and each row of A is multiplied by its own element of b.
- Two different answers. A .* b is [5 10; 18 24], a 2-by-2 matrix. Same inputs, different operations, different sizes; both are valid, so the operator must say which one you mean.
from session import prompt, version
version()
prompt("products.m", errors=1) Output
# GNU Octave (aarch64-apple-darwin25.4.0) version 11.3.0
$ octave-cli --quiet
octave:1> [1 2 3] * [4 5 6]
error: operator *: nonconformant arguments (op1 is 1x3, op2 is 1x3)
octave:2> [1 2 3] .* [4 5 6]
ans =
4 10 18
octave:3> [1 2 3] * [4 5 6]'
ans = 32
octave:4> A = [1 2; 3 4];
octave:5> b = [5; 6];
octave:6> A * b
ans =
17
39
octave:7> A .* b
ans =
5 10
18 24
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 products_session.py
The matrix product: A * B, defined when A has as many columns as B has rows: each element of the result is a row of A times a column of B, added up, so an m-by-n times an n-by-p matrix is m-by-p. A * B needs the columns of A to match the rows of B, and an
m-by-n matrix times an n-by-p matrix gives m-by-p, each element a row of A times a column of B, added up. Two 1x3
rows do not meet that rule, hence operator *: nonconformant arguments (op1 is 1x3, op2 is 1x3). Transposing the
second turns it into a 3x1 column, and [1 2 3] * [4 5 6]' is the dot product: The sum of the products of matching elements of two vectors of one length: [1 2 3] * [4 5 6]' = 1*4 + 2*5 + 3*6 = 32. dot(a, b) computes it for vectors of any orientation.,
1 × 4 + 2 × 5 + 3 × 6 = 32. The element-wise .* needs no such match, only sizes that implicit expansion accepts,
which is why A .* b worked with a 2x2 A and a 2x1 b.
Multiplying two vectors of measurements with *
Power is voltage times current at each sample, p = volts .* amps. Written volts * amps, two rows give an error,
and a row times a column gives a single number or a whole matrix, depending on the order. Use the dot whenever you
mean “at each position”.
Checkpoint
Check: Matrix and element-wise products
Powers and division come in both forms too
The same rule holds for powers and division. A resistor of 4.7 Ω carrying 0.5, 1.2 and 2.0 A dissipates I²R at each current, which is an element-wise square times a scalar. The session computes it and shows each pair of operators:
from session import prompt, version
version()
prompt("powers.m", errors=2) Output
# GNU Octave (aarch64-apple-darwin25.4.0) version 11.3.0
$ octave-cli --quiet
octave:1> A = [1 2; 3 4];
octave:2> A^2
ans =
7 10
15 22
octave:3> A.^2
ans =
1 4
9 16
octave:4> I = [0.5 1.2 2.0];
octave:5> P = I.^2 * 4.7
P =
1.1750 6.7680 18.8000
octave:6> I^2
error: for x^y, only square matrix arguments are permitted and one argument must be scalar. Use .^ for elementwise power.
octave:7> 1 ./ [1 2 4]
ans =
1.0000 0.5000 0.2500
octave:8> 1 / [1 2 4]
error: operator /: nonconformant arguments (op1 is 1x1, op2 is 1x3)
octave:9> A \ [5; 11]
ans =
1
2
octave:10> [5 11] / A
ans =
6.5000 -0.5000
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 powers_session.py
| Operator | Means | In the session |
|---|---|---|
A^2 |
A * A, for a square A |
[7 10; 15 22] |
A.^2 |
each element squared | [1 4; 9 16] |
I.^2 * 4.7 |
each current squared, times a scalar | 1.175 6.768 18.8 watts |
1 ./ x |
each element’s reciprocal | 1 0.5 0.25 |
A \ b |
solves A * x = b for x |
[1; 2] |
b / A |
solves x * A = b for x |
[6.5 -0.5] |
A^2 is a matrix power, A * A, which only a square matrix has, so I^2 on the 1x3 row of currents is an error
whose message says what to do: Use .^ for elementwise power. A scalar factor needs no dot, which is why
I.^2 * 4.7 works: multiplying by a single number is the same in both families. 1 / [1 2 4] fails as well,
because / is the matrix division, while 1 ./ [1 2 4] divides 1 by each element.
The left and right divisions solve linear system: A set of linear equations solved together, written in matrix form as A*x = b. In MATLAB and Octave, x = A \ b solves it without forming an inverse. More in “MATLAB and GNU Octave: one language, two tools” without forming an inverse:
A \ [5; 11] found x = [1; 2], since 1 × 1 + 2 × 2 = 5 and 3 × 1 + 4 × 2 = 11. The linear-algebra module of this
track explains why that is better than inv(A) * b, and what \ does when A is not square.
Checkpoint
Check: Powers and division, both forms
Implicit expansion stretches dimensions of size 1
An element-wise operation needs two arrays of the same size, or sizes it can stretch to the same size. In each
dimension the two sizes must be equal or one of them must be 1, and a size of 1 is stretched to the other. This is
implicit expansion: The rule that lets element-wise operators combine arrays of different sizes: in each dimension the sizes must be equal or one of them 1, and each size of 1 is stretched. Octave's manual calls it broadcasting., called broadcasting in the Octave manual, and MathWorks states
the same rule for MATLAB. A scalar is the simplest case: A + 10 adds 10 to every element. Step through the rule on
the cases of the session below:
The drawing shows the whole scene; its 8 steps are listed below.
Every step as text
- A row plus a column. [1 2 3] + [10; 20] lines up 1x3 against 2x1: rows 1 and 2, columns 3 and 1.
- Both dimensions pass. In each dimension one of the two sizes is 1, so the sizes are compatible and nothing fails.
- Stretched to 2x3. The row is used for both rows and the column for all three columns. Octave does not copy them in memory; it reads them as if they had been repeated.
- The result. Adding position by position gives 11 12 13 and 21 22 23, a 2x3 matrix, as the session printed.
- A table of powers. power_table.m uses the same rule on purpose: V.^2 is 1x3 and R is 3x1, so V.^2 ./ R is a 3x3 table, one row per resistor and one column per voltage.
- Column means. X - mean(X) lines up 3x2 against 1x2. The columns agree and the 1 row of means stretches to 3, so every column loses its own mean.
- Sizes that cannot combine. [1 2 3] + [1 2] lines up 1x3 against 1x2. The columns are 3 and 2 and neither is 1, so Octave stops: nonconformant arguments (op1 is 1x3, op2 is 1x2).
- Legal, but not meant. A 1x100 row minus a 100x1 column passes every check and gives a 100x100 matrix. The rule cannot know that you meant two vectors of the same shape.
% power_table.m: power P = V^2 / R for three voltages and three resistors, without a loop
V = [5 12 24]; % volts, a row
R = [10; 47; 100]; % ohms, a column
P = V.^2 ./ R; % 1x3 against 3x1: a 3x3 table, one row per resistor
printf('size(P) = %s\n', mat2str(size(P)));
printf('%8s %8s %8s %8s\n', 'R \ V', '5 V', '12 V', '24 V');
printf('%6d ohm %8.3f %8.3f %8.3f\n', [R P]'); from session import prompt, run, version
version()
run("octave-cli --no-init-all power_table.m")
prompt("expansion.m", errors=1) Output
# GNU Octave (aarch64-apple-darwin25.4.0) version 11.3.0
$ octave-cli --no-init-all power_table.m
size(P) = [3 3]
R \ V 5 V 12 V 24 V
10 ohm 2.500 14.400 57.600
47 ohm 0.532 3.064 12.255
100 ohm 0.250 1.440 5.760
$ octave-cli --quiet
octave:1> [1 2 3] + [10; 20]
ans =
11 12 13
21 22 23
octave:2> [1 2 3] + [1 2]
error: operator +: nonconformant arguments (op1 is 1x3, op2 is 1x2)
octave:3> X = [1 2; 3 4; 5 6];
octave:4> X - mean(X)
ans =
-2 -2
0 0
2 2
octave:5> bsxfun(@minus, X, mean(X))
ans =
-2 -2
0 0
2 2
octave:6> S = speye(3) + [1 2 3];
octave:7> issparse(S)
ans = 0
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 expansion_session.py
The table is the rule used on purpose. V is a 1x3 row of voltages and R a 3x1 column of resistances, so
V.^2 ./ R is 3x3, one row per resistor: 24 V across 10 Ω gives 57.6 W, across 100 Ω 5.76 W. No loop, and no copy of
V or R is made. X - mean(X) is the same idea on data: mean(X) is a 1x2 row of column means, stretched over
the 3 rows, so each column is centred on its own mean. [1 2 3] + [1 2] cannot combine, 3 against 2 with neither 1,
and stops with nonconformant arguments.
Key idea
Two sizes combine when, in every dimension, they are equal or one of them is 1; each size of 1 is stretched, so a row against a column always gives a matrix.
Octave 11 changed two details of the rule, and older code shows another way to write it:
Octave 11 broadcasts quietly, sparse and diagonal matrices included
Octave 11 no longer prints its Octave:language-extension warning for broadcasting, and broadcasting now also works
with sparse and diagonal matrices, as its release notes list: speye(3) + [1 2 3] ran, giving a full matrix. Old
code written for MATLAB releases before implicit expansion used bsxfun(@minus, X, mean(X)), which still works and
gave the same centred columns in the session.
Checkpoint
Check: Implicit expansion and result sizes
A row minus a column is a matrix, not an error
Implicit expansion cannot tell a table you wanted from a mix-up you did not see. A common way to get the mix-up: the
measurements are a row, as linspace returns its points, and a model function returns a column. The residual
y - f is then a full matrix:
% orientation_bug.m: a residual from a row of measurements and a column of model values
t = linspace(0, 1, 100); % 1x100
y = 2*t + 1 + 0.05*sin(37*t); % measurements, a row like t
f = (2*t + 1)'; % model values, returned as a column
r = y - f; % no error: 1x100 minus 100x1
printf('size(r) = %s\n', mat2str(size(r)));
printf('rms of r = %.4f\n', sqrt(mean(r(:).^2)));
r_ok = y(:) - f(:); % both as columns first
printf('size(r_ok) = %s, rms = %.4f\n', mat2str(size(r_ok)), sqrt(mean(r_ok.^2)));
assert(isequal(size(y), size(f)), 'y is %s but f is %s', mat2str(size(y)), mat2str(size(f))); from session import run, version
version()
run("!octave-cli --no-init-all orientation_bug.m") Output
# GNU Octave (aarch64-apple-darwin25.4.0) version 11.3.0
$ octave-cli --no-init-all orientation_bug.m
size(r) = [100 100]
rms of r = 0.8224
size(r_ok) = [100 1], rms = 0.0355
error: y is [1 100] but f is [100 1]
error: called from
assert at line 109 column 11
orientation_bug at line 10 column 1
[exit status 1]
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 orientation_session.py
r came out 100x100, every measurement minus every model value, and nothing failed. Its root-mean-square error,
0.8224, is 23 times the true 0.0355 that y(:) - f(:) gives, the two vectors turned into columns first. The last line
is the guard: an assertion that the two sizes agree stopped the script with y is [1 100] but f is [100 1].
Subtracting vectors that came from different places
Data from a file, a function and a range can each have their own orientation. Before an element-wise operation on
two vectors, either make both columns with (:), or assert isequal(size(a), size(b)) so that a mix-up stops the
script.
Checkpoint
Check: Rows, columns and accidental matrices
Practise: operators on small data
Do these in Octave, and check each against what Octave prints:
- With
v = [1 2 3]andw = [4; 5; 6], predict the size ofv * w,w * v,v .* w'andv + w, then check. - Build a times table of 1 to 9 with one line,
(1:9)' * (1:9), then with implicit expansion,(1:9)' .* (1:9). Both are 9x9; check that they are equal. - Centre the columns of
X = magic(4)withX - mean(X)and check that every column of the result has mean 0. - Change
orientation_bug.mso thatfis a row, run it, and confirm that the assertion passes and the RMS is 0.0355.
Then try the questions below on operators and expansion; each answer is explained the moment you give it:
Practice that adapts to you
Practise this lesson
12 questions about 4 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.
Interview questions
Warm-up: what is the difference between * and .*?
* is matrix multiplication: A * B needs the columns of A to equal the rows of B, and each element of the result
is a row of A times a column of B, summed, so a 2x2 matrix times a 2x1 column gives a 2x1 column, [17; 39] for
[1 2; 3 4] * [5; 6]. .* multiplies elements in the same position, with no sums, and needs sizes that implicit
expansion accepts, so [1 2 3] .* [4 5 6] is 4 10 18, while [1 2 3] * [4 5 6] is an error. The same pairing
holds for / and ./ and for ^ and .^. For a scalar factor both give the same result. Use the dot whenever the
calculation happens at each position, such as power from voltage and current samples.
Warm-up: what is implicit expansion, and when are two sizes compatible?
Implicit expansion lets element-wise operators combine arrays of different sizes: in every dimension the two sizes must
be equal or one of them 1, and each size of 1 is treated as if it were repeated to match. So a 1x3 row plus a 2x1
column gives a 2x3 matrix, and X - mean(X) subtracts each column’s mean from that column. Octave’s manual adds that
nothing is copied in memory. Sizes such as 1x3 and 1x2 are not compatible and give a nonconformant-arguments error. The
rule is the same in MATLAB and Octave; the trap is that a row and a column of the same length are always compatible, so
a slip in orientation gives a matrix instead of an error.
Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.
-
Fresher to mid
A curve fit reported an RMS error 23 times larger than expected, and nothing failed. You find that the residual r = y - f is 100-by-100. Explain how that happened, why no error appeared, and how you make such mix-ups fail loudly in the future.
-
Mid to senior
You need all distances between 5,000 and 8,000 points in the plane, without loops. Write the expression, say how much memory it needs, and say what you would change for 50,000 by 80,000 points, or for coordinates around a million metres.
Key takeaways
- Operators without a dot follow linear algebra and operators with a dot work position by position:
*,/,^against.*,./,.^. A * Bneeds the columns ofAto match the rows ofB, a row times a column is a dot product, andA \ bsolvesA * x = bwithout an inverse.- Element-wise operations combine sizes that are equal or 1 in every dimension, stretching each size of 1: a row
against a column gives a table, and
X - mean(X)centres columns. - A row minus a column of the same length is a full matrix, not an error, so make vectors columns with
(:)or assert their sizes before combining them.
Terms in this lesson
- dot product
- The sum of the products of matching elements of two vectors of one length: [1 2 3] * [4 5 6]' = 1*4 + 2*5 + 3*6 = 32. dot(a, b) computes it for vectors of any orientation.
- element-wise operation
- An operation done separately at each position of its arrays, such as .*, ./, .^, + and -. [1 2 3] .* [4 5 6] gives 4 10 18; no products are added up.
- implicit expansion
- The rule that lets element-wise operators combine arrays of different sizes: in each dimension the sizes must be equal or one of them 1, and each size of 1 is stretched. Octave's manual calls it broadcasting.
- matrix product
- A * B, defined when A has as many columns as B has rows: each element of the result is a row of A times a column of B, added up, so an m-by-n times an n-by-p matrix is m-by-p.
References
- GNU Octave manual: Arithmetic Operators (The Octave Project Developers)
- GNU Octave manual: Broadcasting (The Octave Project Developers)
- GNU Octave 11 release notes (The Octave Project Developers)
- Array vs. Matrix Operations (MATLAB) (MathWorks)
- Compatible Array Sizes for Basic Operations (MATLAB) (MathWorks)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress