Do NOT reason about parfor classification rules from memory. LLM training
data contains incomplete and often incorrect explanations of these rules.
MATLAB's code analyser (checkcode / codeIssues / mcp__matlab__check_matlab_code)
is the only reliable authority — it implements the exact same classifier that
the runtime uses. Always run it first; never guess.
When to Use
- ANY code involving
parforwhere the user reports a problem or asks for help - User gets a parfor classification error (runtime or in editor)
- User wants to convert a
forloop toparfor - User asks why parfor complains, won't run, or is slow due to classification
- You are about to suggest changes to parfor code — run the analyser first
When NOT to Use
- parfor runs but is slow — performance/pool-choice question, not a classification error
- Worker needs setup/state (paths, environment, toolboxes) — not a parfor variable problem
- Code simply mentions
parforbut the user does not mention any problems with it
Workflow
Always follow this order. Never guess at classification rules.
-
Run the code analyser on the file containing the parfor loop using the
mcp__matlab__check_matlab_codeMCP tool. Do NOT shell out to MATLAB via Bash — always use the MCP tool. Read the exact error message. -
Identify the error type from the message (see table below).
-
Apply the matching fix pattern (see Patterns section).
-
Verify by re-running
mcp__matlab__check_matlab_codeon the fixed code — confirm zero parfor classification errors.
Error Messages and Root Causes
For the full catalog of all 48 parfor analyser messages (with IDs), see
references/parfor-analyser-messages.md. The table below covers the most
common classification errors.
| checkcode message | Root cause | Fix pattern |
|---|---|---|
| "Unable to classify variable 'X'" | Catch-all: many causes. Often invalid slicing (inconsistent subscripts, indexing not involving the loop variable) or invalid combinations of accesses (e.g. indexed reduction). Struct-field accesses are one specific way to trigger this. | Identify the specific access pattern, then restructure to fit one category or split conflicting roles |
| "multiple sliced accesses...do not all have the same list of subscripts" | Same variable sliced with different subscript patterns (e.g. A{i,1}, A{i,2}) |
Use single assignment: A(i,:) = {...} |
| "Variable 'X' is indexed...but it is not a valid sliced output variable" | One subscript is non-deterministic (random, function call), or loop variable appears in multiple dimensions, or subscript pattern otherwise violates slicing rules | Pre-compute problematic subscripts, restructure to use loop variable in exactly one dimension with fixed remaining subscripts |
| "accessed with an invalid combination of sliced indexing expressions and non-indexed reads" | Variable is written with sliced indexing AND read whole in the same iteration — conflicting roles | Separate into two variables: one for sliced writes, a snapshot or broadcast copy for whole reads |
| "The temporary variable 'X' might be used after the PARFOR loop" / "Temporary variable 'X' must be set inside the parfor loop before it is used" | Variable is conditionally assigned (only in an if-branch), so MATLAB treats it as temporary — but it's used after the loop or read before being set on some paths | Restructure as sliced output (store per-iteration values) + post-loop reduction |
| "When indexing a sliced variable with a nested for loop variable, the range...must be a row vector of positive constant numbers or variables" | Inner for j = 1:(expr) where expr is not a literal constant or a broadcast variable |
Make the inner loop range a constant or broadcast variable, or accumulate into a local temporary and assign the sliced variable once per outer iteration |
| "The entire array or structure 'X' is a broadcast variable" | Variable used whole (not indexed by loop variable); read-only | Not an error — performance warning. Use parallel.pool.Constant(X) if large |
Variable Classification Rules (Reference)
parfor classifies each variable into exactly one category:
| Category | Rule | Example |
|---|---|---|
| Loop | The loop index variable | parfor i = 1:N |
| Sliced | Indexed by the loop variable in any one dimension (not just the first); the same subscript pattern is used in every access. The loop variable may be offset by a literal constant or a broadcast variable (e.g. A(i+1,:), A(:,i-k) where k is broadcast). |
A(i,:) = ..., A(:,i) = ..., A(i+1) = ... |
| Broadcast | Used whole (not indexed by loop variable); read-only | total = sum(bigArray) |
| Reduction | Form X = X op expr or X = expr op X where X is the entire variable, op is associative operator +, .*, &, | etc. |
total = total + val |
| Temporary | First use in iteration is an assignment; not used after loop | temp = rand(10) |
Critical constraint: A variable must fit exactly ONE category. Mixed usage (e.g. struct fields used as reductions, or same variable sliced with different subscripts) causes classification failure.
Patterns
Fix 1: Struct field as sliced output → plain array
Error: "Unable to classify variable 'out'"
The most common cause of "Unable to classify" with structs. Users write
out.field(i) = someFcn() expecting to slice into a struct field, but
first-level indexing on out is dot (.field), not parentheses. Sliced
variables require first-level () or {} indexing with the loop variable —
dot-indexing is never valid for slicing.
% BROKEN: first-level subscript on out is dot, not paren
parfor i = 1:N
out.x(i) = computeX(i);
out.y(i) = computeY(i);
end
Fix: Use separate arrays for sliced output, pack into struct after the loop:
xVals = zeros(1, N);
yVals = zeros(1, N);
parfor i = 1:N
xVals(i) = computeX(i);
yVals(i) = computeY(i);
end
out.x = xVals;
out.y = yVals;
A related variant: struct fields used as reductions (results.sum = results.sum + val).
The same rule applies — extract each field into its own scalar variable, reduce
independently, then pack the struct after the loop.
Fix 2: Multiple sliced subscripts → single assignment
Error: "multiple sliced accesses...do not all have the same list of subscripts"
% BROKEN: {i,1}, {i,2}, {i,3} are three different subscript patterns
output = cell(N, 3);
parfor i = 1:N
[a, b, c] = computeStuff(i);
output{i,1} = a;
output{i,2} = b;
output{i,3} = c;
end
Fix: Single parenthesis-indexed assignment with consistent subscripts:
output = cell(N, 3);
parfor i = 1:N
[a, b, c] = computeStuff(i);
output(i,:) = {a, b, c};
end
Note: use () parenthesis indexing (assigns cells), not {} brace indexing when assigning
multiple cells.
Fix 3: Invalid sliced subscript patterns
Error: "Variable 'X' is indexed...but it is not a valid sliced output variable"
A sliced variable must be indexed by the loop variable in exactly one dimension, with the remaining subscripts fixed (literals, broadcast variables, or colon). Three common violations:
parfor i = 1:N
out(i, randi(5)) = val; % (a) non-deterministic subscript
out2(i, 1:4, i) = row; % (b) loop variable in two dimensions
for j = 1:(N-1)
out3(i, j) = fcn(i,j); % (c) nested-for range is not constant
end
end
Fixes:
(a) Pre-compute non-deterministic indices before the parfor, or accumulate into a local temporary and assign a full row once:
parfor i = 1:N
localRow = zeros(1, 5);
localRow(randi(5)) = val;
out(i,:) = localRow;
end
(b) Remove the duplicate loop-variable dimension — restructure so i indexes
only one dimension:
parfor i = 1:N
out2(i,:) = computeRow(i); % i in dim 1 only
end
(c) Make the nested-for range a constant or broadcast variable, OR collect into a local variable and assign the sliced output once:
innerN = N - 1; % broadcast (constant before parfor)
parfor i = 1:N
row = zeros(1, innerN);
for j = 1:innerN
row(j) = fcn(i, j);
end
out3(i, 1:innerN) = row;
end
Fix 4: Sliced write combined with full read
Error: "Unable to classify variable 'data'"
A sliced read combined with a broadcast (whole) read is legal — the variable simply "decays" to broadcast in that case. The unclassifiable combination is a sliced write together with a full read of the same variable in the same iteration. Often the whole read is simply attempting to extract size or type information from the sliced output variable.
% BROKEN: data is sliced-write (data(i,j) = ...) AND read whole (size(data,2))
data = buildMatrix(); %
parfor i = 1:size(data,1)
for j = 1:size(data,2) % This counts as a "full read" of data
data(i,j) = myFunc(i,j);
end
end
Fix: Extract the constant information needed ahead of the loop.
data = buildMatrix();
[N,M] = size(data);
parfor i = 1:N
for j = 1:M
data(i,j) = myFunc(i,j);
end
end
Note that if the "whole read" of a sliced output variable depends on the actual values within the variable, then this fix is not appropriate, and may imply that iterations are not independent.
Fix 5: Reduction not in standard form
Error: "Unable to classify variable 'X'"
A reduction must be exactly: X = X op expr or X = expr op X or X = fcn(X, expr) where X is
the entire variable (no indexing) and op is an associative operator +, .* etc.; fcn can be any
associative function such as union, max etc.
% BROKEN: indexed reduction
counts = zeros(1, 5);
parfor i = 1:100
bin = randi(5);
counts(bin) = counts(bin) + 1; % indexed reduction - not supported
end
Fix: Accumulate per-iteration, then reduce:
counts = zeros(1, 5);
parfor i = 1:100
bin = randi(5);
localCounts = zeros(1, 5);
localCounts(bin) = 1;
counts = counts + localCounts; % whole-variable reduction
end
Fix 6: Temporary variable incorrectly flagged
If a variable is assigned before use in every iteration but checkcode still warns, ensure the first reference in the iteration is a full assignment (not indexed):
% BROKEN: temp is not fully-assigned as first reference
parfor i = 1:10
temp.a = rand();
temp.b = 2*rand();
result(i) = temp.a * temp.b;
end
Fix: Create temp in single assignment
parfor i = 1:N
temp = struct('a', rand(), 'b', 2*rand()); % Ok - single non-indexed assignment
result(i) = sum(temp);
end
Fix 7: Conditional update that needs to survive the loop
Error: "The temporary variable 'X' might be used after the PARFOR loop"
Any temporary variable is unavailable after the loop — this is a fundamental
parfor rule, not specific to conditional assignment. The "best so far" pattern
fails because bestscore = score inside a conditional doesn't match reduction
form (X = X op expr), so the classifier cannot treat it as a reduction. It
falls through to temporary, and temporaries are discarded when the loop ends:
% BROKEN: bestscore doesn't match reduction form — classified as temporary
bestscore = -Inf;
parfor i = 1:N
score = expensiveCompute(i);
if score > bestscore
bestscore = score;
X = candidates(i);
end
end
% bestscore and X are undefined here
Fix: Compute all candidates as sliced output, then reduce after the loop:
scores = zeros(1, N);
allX = cell(1, N);
parfor i = 1:N
scores(i) = expensiveCompute(i);
allX{i} = candidates(i);
end
[bestscore, idx] = max(scores);
X = allX{idx};
Conventions
- ALWAYS run the code analyser before diagnosing. Use
mcp__matlab__check_matlab_code(MCP tool),codeIssues(R2022b+), orcheckcode. The analyser implements the exact same classifier as the runtime — it is the ground truth. Never diagnose from memory alone. - ALWAYS verify the fix with the analyser — re-run on the fixed file until there are no further parfor errors before declaring success. Fixing one problem may reveal other problems.
- Don't run the code — the parallel pool may be slow or unavailable. The code analyser catches all classification errors statically.
- Preserve semantics — the fix must produce the same results as the
original
forloop. - Minimal changes — fix only the classification issue. Don't restructure the entire algorithm unless the user asks.
Copyright 2026 The MathWorks, Inc.