1. Introduction

Understanding Kotlin’s behavior when it comes to pass-by-value and pass-by-reference is crucial to working effectively with the language.

In this tutorial, we’ll explore Kotlin’s behavior for passing parameters. We’ll examine both pass-by-value and pass-by-reference by working through a series of practical examples.

2. Pass-By-Value

In Kotlin functions, as with Java methods, arguments are pass-by-value by default. This means that the value of an argument is passed as the function’s parameter. If we change the value of the parameter within the function, the original value outside the function isn’t affected. The advantage of this mechanism is to prevent accidental changes outside the function.

Before testing this behavior, it’s important to acknowledge that parameters in Kotlin cannot be reassigned. When we attempt to do so, the code will not compile, such as in the following example:

fun modifyValue(a: Int) {
    a = 5
}

With this in mind, let’s define a function that takes an Int parameter, adds 10 to it, and returns the result:

private fun modifyValue(value: Int): Int {
    return value + 10
}

Lastly, let’s assert its behavior with a unit test:

@Test
fun `Test using pass-by-value`(){
    val num = 5
    val modifiedNum = modifyValue(num)
    
    assertEquals(5, num)
    assertEquals(15, modifiedNum)
}

First, we call the modifyValue() function, passing the variable num. Then, we assert that the value of num remains unchanged, whereas the modifiedNum variable should have a value of 15 as resolved from the function call.

3. Pass-By-Reference

Pass-by-reference is the behavior where a function receives the memory address of an argument, allowing access and modification.

Java and Kotlin always use pass-by-value. However, when passing objects or non-primitive types, the function copies the reference, simulating pass-by-reference. Changes inside the method affect the external object due to the shared reference.

Let’s illustrate this behavior with a simple example:

data class SomeObj(var x: Int = 0)

private fun modifyObject(someObj: SomeObj) {
    someObj.x = 3
}

First, we define the data class SomeObj having one property x of type Int with a default value of 0.

Then, we create a function named modifyObject() that takes a parameter o of type SomeObj. Within the function, the value of property x in object o is updated to 3.

To verify that changes to the object inside the function affect the original object outside, let’s write a unit test:

@Test
fun `Test using pass-by-reference`() {
    val obj = SomeObj()
    
    assertEquals(0, obj.x) // before modify

    modifyObject(obj)

    assertEquals(3, obj.x) // after modify
}

Then, we assert that x in obj is still 0 before calling the modifyObject() function.

Kotlin and Java both use the same memory model, where the stack stores the reference variable, and the heap stores the objects. Our newly created object contains a reference to obj:

 

diagram1

Finally, we call the modifyObject() function and assert that x has changed to 3.

In our memory model, we now have two objects on the heap: the one inside the function and the one outside. When the one inside the function is created, a new reference variable is copied onto the stack. Two reference variables now exist on the stack with identical values:

diagram2

Note that when the function call occurs, the reference of obj is copied and sent as an argument to the modifyObject() function. This is different from the original pass-by-reference, where the argument is the original reference.

4. Side-By-Side Comparison

Let’s explore the key differences between pass-by-value and pass-by-reference using a table, covering the mechanism that drives the behavior, the impact on original values, and its common usage:

Criteria Pass-By-Value Pass-By-Reference Pass-By-Value with Reference Copy
Mechanism Value of the parameter is passed into the function Object reference or memory address is passed into the function Value of the object’s reference is copied and passed by value
Original Value Changes Does not affect the original value May affect the original value Changes are visible outside
Safety Avoids accidental errors or unwanted changes to the value Potential for accidental changes outside the function Provides safety by not affecting the original value
Predictability Easier to predict function behavior Code may result in unexpected behavior Enhances predictability by maintaining the independence of the original value

5. Conclusion

In this article, we’ve shown that Kotlin defaults to pass-by-value for function arguments that are primitives. We also observed that for objects and non-primitives, in both Java and Kotlin, we have pass-by-value with reference copy, which behaves similarly to pass-by-reference.

In Kotlin, we must be aware of whether our functions deal with primitive or non-primitive arguments to understand which of the two behaviors is applied.

As always, the full source code is available over on GitHub.

Comments are closed on this article!