Skip to content

05 · Functions in MATLAB

Basic function syntax

A function file's name must match its function name exactly (square_it.m must define function ... = square_it(...)):

% square_it.m
function result = square_it(x)
    result = x^2;
end
>> square_it(5)
ans =

    25

Since R2016b, you can also define local functions inside a script file (after the main script code) or write a whole file as a function file where the function itself is the entire file — both are common; script-local functions are convenient for helper logic you don't want to publish as its own file.

Multiple input and output arguments

function [total, average] = summarize(values)
    total = sum(values);
    average = mean(values);
end
>> [t, a] = summarize([10, 20, 30])
t =

    60

a =

    20

Calling summarize([10, 20, 30]) with only one output captures just total — MATLAB doesn't error, it silently gives you the first output only:

>> t = summarize([10, 20, 30])
t =

    60

If you want the second output but not the first, use ~ as a placeholder:

>> [~, a] = summarize([10, 20, 30])
a =

    20

Default-like behavior with nargin

MATLAB has no default-parameter syntax in the function signature itself. Instead, nargin (a built-in that reports how many inputs were actually passed) lets a function detect a missing argument and supply its own default:

function y = scale_value(x, factor)
    if nargin < 2
        factor = 2;    % default when the caller omits the second argument
    end
    y = x * factor;
end
>> scale_value(5)
ans =

    10

>> scale_value(5, 3)
ans =

    15

nargin (with no arguments, called from inside a function) always reports how many arguments this specific call actually supplied — not how many the function is declared to accept.

nargout — how many outputs the caller wants

Symmetrically, nargout reports how many output values the caller requested, which matters if computing a second output is expensive and should be skipped when not needed:

function [result, details] = analyze(x)
    result = mean(x);
    if nargout > 1
        details = struct('min', min(x), 'max', max(x));  % only computed if asked for
    end
end
>> m = analyze([1 5 9])
m =

     5

>> [m, d] = analyze([1 5 9])
m =

     5

d =

  struct with fields:

    min: 1
    max: 9

Variable numbers of arguments: varargin and varargout

function total = add_all(varargin)
    total = 0;
    for i = 1:length(varargin)
        total = total + varargin{i};
    end
end
>> add_all(1, 2, 3)
ans =

     6

>> add_all(1, 2, 3, 4, 5)
ans =

    15

varargin collects any number of trailing arguments into a cell array (hence indexing with curly braces {i}, not parentheses — cell arrays are covered fully in Level 2). varargin must be the last parameter in the function's signature; any named parameters before it are matched normally.

Anonymous functions

For small, throwaway expressions, an anonymous function avoids creating a separate .m file:

>> square = @(x) x^2;
>> square(5)
ans =

    25

>> add = @(x, y) x + y;
>> add(3, 4)
ans =

     7

Anonymous functions capture the value of any variable from the enclosing workspace at the moment they're defined — changing that variable afterward does not affect the already-created function:

>> factor = 10;
>> multiply = @(x) x * factor;
>> factor = 100;       % changing factor AFTER defining multiply...
>> multiply(5)          % ...has no effect: still uses factor=10, captured earlier
ans =

    50

This is a common surprise: multiply "remembers" factor as it was at definition time, not as a live reference to the variable.

Local scope — functions don't see the caller's workspace

function show_x()
    disp(exist('x', 'var'))    % 0 if 'x' does not exist in THIS function's scope
end
>> x = 42;
>> show_x()
     0

Even though x exists in the base workspace when show_x() is called, the function has its own separate workspace and cannot see x unless it's passed in as an argument — this isolation is exactly what makes functions safer and more predictable to reason about than scripts, which do share the base workspace.

Recursion

Functions can call themselves, as in any language:

function result = factorial_r(n)
    if n <= 1
        result = 1;
    else
        result = n * factorial_r(n - 1);
    end
end
>> factorial_r(5)
ans =

   120

(MATLAB also has a built-in factorial() — this is purely for illustrating recursion.)

Function cheat sheet

Task Syntax
Basic function function out = name(in) ... end
Multiple outputs function [a, b] = name(x) ... end
Call, ignore an output [~, b] = name(x)
Count of inputs actually passed nargin
Count of outputs requested nargout
Default value for omitted arg if nargin < N, arg = default; end
Variable-length input function out = f(varargin) ... end
Anonymous function f = @(x) x^2;
Check if a variable exists in scope exist('name', 'var')

How It Actually Works

MATLAB functions are pass-by-value with lazy copy-on-write, exactly like variable assignment (Module 02). When you call f(A) with a large matrix A, MATLAB does not duplicate A's data buffer to build the function's local copy — it passes a reference with an incremented refcount. Only if the function body actually writes to its parameter (A(1) = 0) does MATLAB allocate a private buffer for that modified copy, at that exact line, and leave the caller's original A untouched. This is why MATLAB functions can accept gigabyte-sized arrays "by value" without the performance cliff pass-by-value would imply in a language that copies eagerly — the copy only happens if and when it's needed, and never for read-only parameters.

Each function has its own workspace (variable table), completely isolated from the caller's and from other functions', which is what makes recursion safe — every recursive call gets a fresh workspace pushed onto MATLAB's call stack, so a variable named n in one recursive frame never collides with n in another. Local functions declared in the same file as a script (post-R2016b) are still separately scoped, resolved by the parser at parse time based on function name, not dynamically at call time.

Nested functions are the one exception: they share their parent function's workspace by reference rather than getting a private copy, implemented via a shared "workspace" data structure the nested function closes over — this is MATLAB's version of a closure, and it's why a nested function can see and mutate a variable declared in its enclosing function without it being passed as an argument.

Note: derived from MATLAB's documented copy-on-write and scoping rules, not run in MATLAB itself.

🔀 See this in another language

Exercise

Write a function stats_report(values, verbose) where verbose is optional (default false, via nargin). It should always return the mean as its first output; when verbose is true, it should also return a second output — a struct with min, max, and std fields — but only compute that struct when nargout > 1 (i.e., only when the caller actually asks for it). Test it three ways: m = stats_report([2 4 6 8]), [m, d] = stats_report([2 4 6 8], true), and confirm calling with one output never triggers the struct computation (add a disp('computing details') inside the nargout > 1 branch to observe this).