Convert MATLAB for Loops to Python: 12 Patterns and Their Pitfalls
Every common MATLAB for-loop pattern translated to Python — with the bugs to watch for in each. Covers range loops, reverse iteration, cell iteration, nested loops, and vectorization.
The surface-level translation
The simplest MATLAB for loop maps directly to Python:
`matlab
for i = 1:10
disp(i);
end
`
`python
for i in range(1, 11):
print(i)
`
Two things to notice immediately:
- MATLAB 1:10 is inclusive of 10 — produces 10 values.
- Python range(1, 11) is exclusive of 11 — also produces 10 values.
The end bound shifts by 1. Miss that and your loop runs one iteration short.
Pattern 1: Range with step
`matlab
for i = 1:2:10 % 1, 3, 5, 7, 9
...
end
`
`python
for i in range(1, 11, 2): # 1, 3, 5, 7, 9
...
`
The three-argument form is range(start, stop, step). Same pitfall as the two-arg form — stop is exclusive. If your MATLAB code does 1:2:10, Python needs range(1, 11, 2).
Pattern 2: Reverse iteration
`matlab
for i = 10:-1:1
...
end
`
`python
for i in range(10, 0, -1):
...
`
Or more Pythonic:
`python
for i in reversed(range(1, 11)):
...
`
The negative step still uses exclusive bound — range(10, 0, -1) stops at 1 (not 0). Watch the bound carefully.
Pattern 3: Fractional step
`matlab
for t = 0:0.1:1 % 0.0, 0.1, ..., 1.0
...
end
`
`python
import numpy as np
for t in np.arange(0, 1 + 0.1, 0.1):
...
`
Python's built-in range only accepts integer steps. For fractional steps you need np.arange. Note the 1 + 0.1 — same inclusive-to-exclusive shift, but with the step as the delta.
Better yet, use np.linspace when you know the count:
`python
for t in np.linspace(0, 1, 11): # 11 values from 0 to 1 inclusive
...
`
Linspace avoids floating-point drift that arange can introduce on long sequences.
Pattern 4: Iterating over a vector directly
`matlab
v = [10 20 30 40];
for x = v
disp(x);
end
`
`python
v = [10, 20, 30, 40]
for x in v:
print(x)
`
MATLAB iterates by columns of the input — so if v is a row vector, each iteration gives a scalar. If v is a matrix, each iteration gives a column vector. Python's for x in v on a 1D array iterates scalars, but on a 2D numpy array it iterates rows — opposite of MATLAB. Convert matrices with v.T before iterating if you need the column-wise behavior.
Pattern 5: Iterate with index AND value
`matlab
for i = 1:length(names)
fprintf('%d: %s\n', i, names{i});
end
`
`python
for i, name in enumerate(names, start=1):
print(f'{i}: {name}')
`
Python's enumerate is cleaner than MATLAB's for i = 1:length(...) idiom. Use start=1 if you want MATLAB-style 1-indexed output, or the default 0 for zero-indexed.
Pattern 6: Nested loops
`matlab
for i = 1:rows
for j = 1:cols
A(i, j) = f(i, j);
end
end
`
`python
for i in range(rows):
for j in range(cols):
A[i, j] = f(i + 1, j + 1)
`
If your f expects MATLAB-style 1-indexed inputs, pass i + 1 and j + 1. If you've also ported f, drop the +1.
Vectorize whenever possible. Nested loops in NumPy are dramatically slower than their MATLAB equivalents. Most A(i, j) = f(i, j) patterns can be rewritten as a single array operation:
`python
i_grid, j_grid = np.meshgrid(np.arange(rows), np.arange(cols), indexing='ij')
A = f(i_grid, j_grid)
`
That's the same loop in one line and 100× faster.
Pattern 7: Loop over cell array
`matlab
names = {'Alice', 'Bob', 'Carol'};
for i = 1:length(names)
disp(names{i});
end
`
The Python list equivalent is straightforward:
`python
names = ['Alice', 'Bob', 'Carol']
for name in names:
print(name)
`
If the MATLAB code relies on cell-array semantics (mixed types, grow-on-write with {end+1} = ), you can pip install matlabtopython-compat and use our CellArray helper — it gives you MATLAB's cell-array ergonomics on a plain Python list.
Pattern 8: Skip iterations with continue
`matlab
for i = 1:n
if isnan(x(i))
continue;
end
total = total + x(i);
end
`
`python
for i in range(n):
if np.isnan(x[i]):
continue
total += x[i]
`
MATLAB's continue behaves identically to Python's. The more idiomatic Python version filters first:
`python
total = sum(xi for xi in x if not np.isnan(xi))
# or just:
total = np.nansum(x)
`
np.nansum ignores NaN values directly. Any time you see a MATLAB loop summing with NaN guards, check if a nan* function (nansum, nanmean, nanstd) replaces the whole loop.
Pattern 9: Early exit with break
`matlab
for i = 1:n
if x(i) > threshold
idx = i;
break;
end
end
`
`python
for i in range(n):
if x[i] > threshold:
idx = i + 1 # if you need MATLAB-style 1-based idx
break
`
Or better:
`python
idx = next((i for i, xi in enumerate(x) if xi > threshold), None)
`
The next() with a generator returns the first match or None — no loop needed. NumPy users may reach for np.argmax(x > threshold) which returns the index of the first True value.
Pattern 10: Accumulator loop → vectorize
This is the single biggest speedup when porting MATLAB:
`matlab
y = zeros(size(x));
for i = 1:length(x)
y(i) = x(i)^2 + 2*x(i) + 1;
end
`
NumPy eliminates the loop entirely:
`python
y = x**2 + 2*x + 1
`
Same result, 100–1000× faster on real data. MATLAB's JIT is excellent at vectorizing simple loops automatically; NumPy only vectorizes if you write it vectorized. When you port, always ask: "can I express this as an array op?"
Pattern 11: Parallel for (parfor)
MATLAB's parfor parallelizes across CPU cores with zero code change:
`matlab
parfor i = 1:n
y(i) = expensive(x(i));
end
`
Python has no built-in equivalent. The closest idiom is concurrent.futures:
`python
from concurrent.futures import ProcessPoolExecutor
with ProcessPoolExecutor() as pool:
y = list(pool.map(expensive, x))
`
Or joblib if you want MATLAB-like ergonomics:
`python
from joblib import Parallel, delayed
y = Parallel(n_jobs=-1)(delayed(expensive)(xi) for xi in x)
`
Our converter flags every parfor as a WARNING because the threading model differs. Don't assume parallelization is free in Python.
Pattern 12: while-true with manual break
`matlab
while 1
x = next_iteration(x);
if converged(x)
break;
end
end
`
`python
while True:
x = next_iteration(x)
if converged(x):
break
`
MATLAB's while 1 and Python's while True are interchangeable. The 1 in MATLAB is a number treated as true; Python prefers the literal True for readability, but while 1 also works.
Should you let a converter do this?
For the 12 patterns above, yes. [Our converter](/convert) handles all of them correctly, applies the 1-indexed to 0-indexed shift consistently, and flags edge cases like grow-on-write arrays and parfor. The vectorization step (Pattern 10) is the only one it can't automate — that's a design decision, not a translation.
For tricky cases — nested callbacks, closures over loop variables, custom iterators — the converter flags them for manual review. The goal is always "here's mechanical Python; here's what needs human judgment" rather than silent guesses.
Free for 50 lines. No account required.
More like this, once a week
New articles on MATLAB-to-Python migration. Short, practical, no fluff — the same tone as the one you just read.