Passing Variables By Reference And By Value
This page describes the difference between passing parameters ByRef and ByVal.
Many, if not most, procedures accept input parameters that are used to calculate the result of a function or
otherwise direct the action of a procedure. These parameters may be passed By Reference (ByRef) or By Value
(ByVal). 
TERMINOLOGY NOTE: Strictly speaking, the term argument refers to passed values from the perspective of the passing-from
procedure. For example, the calling procedure CallingSub passes arguments 
Arg1 and Arg2  to the procedure
CalledSub. The term parameter refers to values passed into a called 
procedure, from the perspective of the called procedure. For example, the procedure CalledSub 
accepts parameters X and Y from
CallingSub. However, these terms are used interchangably in most documentation and will
be used interchangably on this site. The meaning will be clear from the context provided by the narrative text.

Passing ByRef or ByVal indicates whether the actual value of an argument is
passed to the CalledProcedure by the CallingProcedure, or whether a reference
(called a pointer in some other languages) is passed to to the CalledProcedure. If an 
argument is passed ByRef, the memory address of the argument is passed to the 
CalledProcedure and any modification to that parameter by the CalledProcedure
is made to the value in the CallingProcedure. If an argument is passed ByVal,
the actual value, not a reference to the variable, is passed to the CalledProcedure.
A simple example will illustrate this clearly:
    Sub CallingProcedure()
        Dim A As Long
        Dim B As Long
        A = 123
        B = 456
        Debug.Print "BEFORE CALL = A: " & CStr(A), "B: " & CStr(B)
        CalledProcedure X:=A, Y:=B
        Debug.Print "AFTER CALL =  A: " & CStr(A), "B: " & CStr(B)
    End Sub
    
    Sub CalledProcedure(ByRef X As Long, ByVal Y As Long)
        X = 321
        Y = 654
    End Sub
In the 
CallingProcedure the variables 
A and 
B are
assigned the values 123 and 456, respectively. These values are confirmed with the first 
Debug.Print 
statement. Then, the 
CalledProcedure is called passing arguments 
A and 
B. Within 
CalledProcedure the parameters 
X and
Y are assigned the values 321 and 654 respectively, and control is returned back to the procedure
CallingProcedure. Since the parameter 
X was declared with 
ByRef, a reference or pointer to 
A was passed to 
CalledProcedure
and any modification to the 
X parameter in 
CalledProcedure affects the variable
A  in 
CallingProcedure. The parameter 
Y was declared
with 
ByVal, so only the actual value of 
B was passed to 
CalledProcedure. Changes made to the parameter 
Y are not made to the variable
B. This is illustrated by the second 
Debug.Print statement. This shows that
A was modified by 
CalledProcedure but that 
B was not changed.

Objects are always passed by reference. The ByRef and ByVal 
modifers indicate how the reference is passed to the called procedure. When you pass an object type variable to a procedure, the
reference or address to the object is passed -- you never really pass the object itself. When you pass an object 
ByRef, the reference is passed by reference and the called procedure can change the object to which
that reference refers to. When an object is passed ByVal an copy of the reference (address) of the
object is passed.
As is so often the case, an example will serve well to illustrate this:
    Sub CallingProcedure()
        Dim Range1 As Range
        Dim Range2 As Range
        Set Range1 = Range("A1")
        Set Range2 = Range("A2")
        Range1.Value = 123
        Range2.Value = 456
        '''''''''''''''
        ' Debug Group 1
        '''''''''''''''
        Debug.Print "BEFORE CALL::Range1: " & Range1.Address(False, False) & " = " & Range1.Value
        Debug.Print "BEFORE CALL::Range2: " & Range2.Address(False, False) & " = " & Range2.Value
        CalledProcedure R1:=Range1, R2:=Range2
        '''''''''''''''
        ' Debug Group 2
        '''''''''''''''
        Debug.Print "AFTER  CALL::Range1: " & Range1.Address(False, False) & " = " & Range1.Value
        Debug.Print "AFTER  CALL::Range2: " & Range2.Address(False, False) & " = " & Range2.Value
    End Sub
    
    Sub CalledProcedure(ByRef R1 As Range, ByVal R2 As Range)
        R1.Value = 321
        R2.Value = 654
        
        Set R1 = Range("A3")
        Set R2 = Range("A4")
    End Sub
In the CallingProcedure, the variable 
Range1 is set to 
Range("A1") and 
the variable 
Range2 is set to 
Range("A2"). Then 
CalledProcedure
is called, passing 
ByRef R1 and 
ByVal R2. The 
CalledProcedure sets the values of these ranges to new values and then changes the ranges to which
R1 and 
R2 refer. Since 
R1 was passed 
ByRef, the 
CalledProcedure can change the cell to which 
Range1 in 
CallingProcedure refers to. As is confirmed by the second group
of 
Debug.Print statements, the variable 
Range1 now refers to (points to)
Range("A3"), not 
Range("A1"). However, since 
R2 was
passed 
ByVal, the 
CalledProcedure cannot change the range to which 
Range2 refers to in 
CallingProcedure. 

The rules for passing your own custom classes are the same for passing object type variables.

Throughout this article, we have discussed that you cannot change the value of a variable in the calling procedure if a
parameter referring to that variable was passed ByVal. It is worth noting, for clarity, though, that you 
can certainly change the value of a parameter variable regardless of whether is was passed by ByRef or
ByVal. These declarations refer to only if the variable in the calling procedure can be changed.
You can always change the value of a parameter in the called procedure. For example,
    Sub CallingProcedure()
        Dim A As Long
        Dim B As Long
        A = 1
        B = 2
        CalledProcedure X:=A, Y:=A
    End Sub
    
    Sub CalledProcedure(ByVal X As Long, ByVal Y)
        Debug.Print "PRE::  X = " & CStr(X), "Y = " & CStr(Y)
        X = 12345
        Y = 54321
        Debug.Print "POST:: X = " & CStr(X), "Y = " & CStr(Y)
    End Sub
In this code, the 
CalledProcedure is free to change the values of its parameters
X and 
Y, and indeed this is often quite useful. The 
ByRef and 
ByVal declaration affect 
only whether changes
to parameters affect the variables in the 
CallingProcedure. This same rule applies to object
type parameters:
    Sub CallingProcedure()
        Dim Range1 As Range
        Dim Range2 As Range
        Set Range1 = Range("A1")
        Set Range2 = Range("A2")
        CalledProcedure R1:=Range1, R2:=Range2
    End Sub
    
    Sub CalledProcedure(R1 As Range, R2 As Range)
        Debug.Print "PRE::  R1: " & R1.Address(False, False), "  R2: " & R2.Address(False, False)
        Set R1 = Range("Z1")
        Set r2 = Range("Z2")
        Debug.Print "POST:: R1: " & R1.Address(False, False), "  R2: " & R2.Address(False, False)
    End Sub
This code clearly illustrates that the object parameters 
R1 and 
R2 can 
be changed within the scope of the 
CalledProcedure procedure. 
ByRef and
ByVal determine only if the changes to the parameters make their way back to the calling procedure.

Even if a called procedure has declared its parameters as ByRef, you can force those to be 
ByVal by enclosing each argument within parentheses. First, examine the following code. It should look
quite familiar as we have used it before:
Sub CallingProcedure()
    Dim A As Long
    Dim B As Long
    A = 123
    B = 456
    Debug.Print "PRE::  A: " & CStr(A) & "  B: " & CStr(B)
    CalledProcedure X:=A, Y:=B
    Debug.Print "POST:: A: " & CStr(A) & "  B: " & CStr(B)
End Sub
Sub CalledProcedure(ByRef X As Long, ByRef Y As Long)
    X = 321
    Y = 654
End Sub
The 
CalledProcedure changes the values of its parameters to 321 and 654 respectively. However, imagine
the situation in which you do want to call on the functionality of 
CalledProcedure but you don't want
that procedure to modify the input parameters. The difficult way would be to save the original copies of 
A
and 
B and restore the values after 
CalledProcedure returns, or to even rewrite
the 
CalledProcedures procedure altogether. However, VB/VBA gives you a much simpler method: just enclose the
arguments individually in parentheses. For example, 
Sub CallingProcedure()
    Dim A As Long
    Dim B As Long
    A = 123
    B = 456
    Debug.Print "PRE::  A: " & CStr(A) & "  B: " & CStr(B)
    CalledProcedure (A), (B)
    Debug.Print "POST:: A: " & CStr(A) & "  B: " & CStr(B)
End Sub
Sub CalledProcedure(ByRef X As Long, ByRef Y As Long)
    X = 321
    Y = 654
End Sub
Here, the second 
Debug.Print statement illustrates that the values of 
A and 
B have not had their values changed. However, there are a few caveats to using this approach:
- You cannot use named arguments in the call to the CalledFunction.
 
- You (generally) cannot pass Object type parameters in this manner.
 
- In the circumstances in which you can use object type variable (when the object has a default property and the parameter
as declared as Variants), you may very well get incorrect results or a run time error because the 
CalledProcedure is designed to work with object not simple type properties of objects.
 

By default, all parameters are passed by reference, so it is not necessary to include the 
ByRef 
declaration. However, I have over my 20 years as a professional programmer found that it is good practice to include the 
ByRef declaration if you are going to change the value of the parameter. It makes no difference to how
the code runs -- in makes the code neither faster nor slower -- but it serves as documentation that the variable in the calling
procedure is going to be modified by the called procedure. It helps make the code self-documenting. You may or may not agree. Use it 
or don't -- whatever style you prefer. It is worth noting that in VBNET, the default method of passing variables
is 
ByVal, since everything in VBNET is a object. This may be of importance if you
are porting some VBA code to a VBNET-based document, component, or add-in.

Arrays are always passed by reference. You will receive a compiler error if you attempt to pass an array by value. See 
Passing And Returning Arrays With Functions for details about passing and returning
array for VBA procedures.
This page last updated: 10-January-2013