In this article, we’ll continue on the topic of the assignments, which we started in the previous article while talking about tuples. This article’s topic will focus on possible challenges when dealing with arrays and structs and the right approaches to resolve them.
It’s part of our long-standing tradition to make this (and other) articles a faithful companion, or a supplement to the official Solidity documentation.

Value Types vs. Reference Types
Solidity assignment operations are simple and intuitive to understand when working with primitive value types. But as soon as we switch to more complex reference types, complications may appear.

These complications particularly originate from the semantics of assignments for non-value types, for instance, arrays and structs, but also manifest for types like bytes and string.
Before we continue, let’s shortly remind ourselves of the main difference between the value types and the non-value, i.e., reference types.
Reminder: π‘
- Solidity value types are signed integers (
int
), unsigned integers (uint
), Boolean (bool
), addresses (address
), enums (enum
), and bytes (bytes
). - Solidity reference types are fixed-sized arrays, dynamic arrays, array members, byte arrays, string arrays, structs, and mappings.
The main difference between the value-typed variables and reference-typed variables in Solidity is noticeable when passing them as arguments to functions or assigning their values to other variables.
Value types are assigned by copying (deep copy) to other variables or arguments so that any modification on the underlying, original variable won’t have any interaction with the new variable; and vice versa.
On the other hand, a reference type is a variable/name pointed to an object; when doing assignments to a reference type, every additional name will attach to this instance (shallow copy) and share it with all the other names.
Any modification done via any of the names will be reflected on all the other names.
Practical Example of Assignments

In the following example, we’ll analyze the common complications that can show up for arrays and structs, the most commonly used reference types. We’ll look at this example from the Solidity docs:
// SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.4.22 <0.9.0; contract C { uint[20] x; function f() public { g(x); h(x); } function g(uint[20] memory y) internal pure { y[2] = 3; } function h(uint[20] storage y) internal { y[3] = 4; } }
Let’s break this down step by step:
// SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.4.22 <0.9.0;
A regular contract definition, but with several functions doing almost the same thing, however using different memory areas.
contract C { uint[20] x;
Defines an umbrella function calling both functions, g(...)
and f(...)
with the same argument, x
.
function f() public { g(x); h(x); }
Defines a function whose parameter is defined in memory; then it assigns a value to the parameter but doesn’t modify the original object in the storage memory area.
function g(uint[20] memory y) internal pure { y[2] = 3; }
Defines a function whose parameter is defined in storage; then it assigns a value to the parameter and modifies the original object in the storage memory area.
function h(uint[20] storage y) internal { y[3] = 4; } }
With function g(...)
we can notice a typical Solidity mechanism at work: assignment from one memory area to another will trigger copying of the object.
In our example, a storage variable, i.e., an array x
is copied to a function parameter y
, declared in memory. Because of that, the behavior of function g(...)
is limited to its y
variable, and the x
variable stays intact.
The function h(...)
has a parameter stored in the storage memory area. Since it’s in the same memory area as the argument being passed to the function call (x
from storage), both names point to the same object in memory (no copying occurred).
That’s the reason why the function h(...)
modifies the original array x
in the contract, while the function g(...)
leaves it intact.
Summary
In cases like the one above, it’s easy to notice the potential caveats of using assignments.
First, we must pay attention to whether we’re working with value types or reference types.
If we’re working with value types, our values will always be copied when assigned and there’s nothing to worry about in terms of accidentally modifying the original object.
Second, if we’re working with reference types, we must pay attention to the memory area of all the names/variables we’re assigning to.
Third, if we’re assigning between variables in the same memory area, we must be aware they’ll all point to the same physical object (in memory), and the changes on one of the names/variables will be visible through all the names sharing the object.
Fourth, assignment between different memory area objects will trigger object copying, so in that sense, the original object is safe and all the modifications are done on the new (copied) object. However, we should still be observant, because it’s possible we’re expecting our original object to be changed by a function, and the change didn’t reach it because the original and the copied object are physically decoupled.
In short, we should be aware of the simple mechanism described above and make sure it aligns with our needs and expectations.
Conclusion
In this article, we analyzed how assignments between different classes of objects (value types and reference types work). Then we expanded the analysis and considered the memory area of objects. Finally, we reached four simple steps to follow when assigning the objects to variables.
First, we reminded ourselves what value types and reference types are. We also did a short, illustrative comparison of the two.
Second, we went through a cool and simple example to show the contrasting behavior and underline the potential complications with assignments between both categories.
What’s Next?
This tutorial is part of our extended Solidity documentation with videos and more accessible examples and explanations. You can navigate the series here (all links open in a new tab):