This is a more involved subject that it may seem at first. Partly because it isn't documented fully in MATLAB docs, and partly because the sharing mechanisms behind the scenes have changed over the years. First I will describe briefly what a MATLAB variable is. Then I will describe the various sharing mechanisms that MATLAB uses. And finally I will describe how these sharing mechanisms are used in MATLAB behind the scenes.
A MATLAB variable:
A MATLAB variable is basically a C struct called an mxArray with various fields for holding information such as size, class, storage class, and data pointers. The address of this C struct is typically known as the "Structure Address" of the variable, and the data pointers known typically as "Pr", "Pi", "Ir", "Jc", etc. For later versions of MATLAB complex data is interleaved and there is no Pi pointer. For the intrinsic numeric, logical, and char classes, the data lives directly behind the Pr and Pi data pointers (and Ir and Jc pointers for sparse variable indexing). For OOP classdef class variables, there is a proprietary structure behind the data pointers where the actual data lives and the user does not have direct access to this (a fundamental flaw IMO that limits the usefulness of OOP classdef variables in mex routines).
Variable sharing:
MATLAB shares variables in these ways:
Deep Copy: The variable in question is not sharing anything with any other variable.
Shared Data Copy: Multiple variables can have different Structure Addresses but have the same data pointers. E.g., this is what typically results from a direct whole variable assignment or a reshape of a full variable. There used to be a field in the mxArray (CrossRef) that was part of a linked list of all of these variables. Later versions of MATLAB only have a counter to tell you how many variables are part of the list, but the list itself is no longer accessible to the user.
Reference Copy: Multiple variables can have the exact same Structure Address. A field in the mxArray (refcount) indicates how many variables are sharing the same Structure Address. This is what is typically used for cell or struct variable elements.
Parent Copy: Not really a copy in and of itself like above, but in nested structs and cell arrays, variables can end up being shared with variables in other parts of the variable or other variables because of upstream sharing. There is no indication of this in the mxArray itself. I.e., the CrossRef and refcount will appear to be unshared, but sharing is actually taking place.
Handle Copy: If OOP classdef variables are derived from handle, then multiple variables are essentially shared. There will be no indication of this in the mxArray itself, and these variables do not follow the normal "copy-on-write" or "lazy-copy" rules.
When is sharing used?
This is where it gets sticky. The rules are not published, and have changed over the years. The best I can do is give examples:
-- Shared Data Copy Examples --
A = B; % direct whole variable assignment (earlier versions of MATLAB)
A{1} = B; % assigning from workspace into cell or struct (earlier versions of MATLAB)
A = reshape(B,whatever); % reshape of full variable
B{1} % cell or struct element in expression or assignment
fun(B); % function arguments are passed as shared data copies of original
A = typecast(B,'whatever'); % later versions of MATLAB only. Early versions did deep copy.
-- Reference Copy Examples --
A = B; % direct whole variable assignment (later versions of MATLAB)
A{1} = B{1}; % assignment among cell or struct elements
A = 1:5; % literal assignment of small variable can result in background reference copy
-- Parent Copy Example --
A.x = 5; B = A; % A.x is sharing with B.x through the parent A and B sharing.
The original question:
Non-Mex Function arguments are passed into functions via some type of copy mechanism. Whether it is literal variables or varargin, typically shared data copies are used (either for the explicit arguments or as the result of building the varargin cell array). The only exception I have seen to this is sometimes nested functions can pass a deep copy of a scalar variable instead of a shared data copy. So the "copy-on-write" or "lazy-copy" mechanism applies to both literal arguments and varargin arguments inside of a function, because in both cases you are actually working with shared data copies (or perhaps reference copies in later versions of MATLAB) of the originals inside the function. A caveat to this is that if you use special syntax in your function calls you can get the MATLAB parser to recognize that you are trying to modify a variable "in-place" and avoid the deep copy which would otherwise occur.
Mex Function arguments have been somewhat different. Older versions of MATLAB always used to pass in the original variable Structure Address, but later versions of MATLAB use the same rules as Non-Mex functions and pass in shared data copies (although scalars might be passed in as deep copies).
So the "copy-on-write" or "lazy-copy" mechanism in functions is really nothing special. A shared data copy or reference copy of the original variable was passed in. So if no changes are made to it, no deep copy will be made inside the function. If you do change elements of the argument variable, then a deep copy will be made first (i.e., unshared). But this is the behavior that happens at any level of MATLAB ... if you change an element of a shared variable then a deep copy must be made first. It is the same rule applied whether you are inside a function or not ... if the variable is shared and you change an element then a deep copy will be made first.