Simit and a Berliner

A personal blog by Ahmet Kerem Aksoy.

Two simits and a berliner.

WAT


Last week, I was implementing a search history feature for a project at work. I used a map, which will eventually be a local storage or session storage, to store the user query history in the application. Until to the point where I was storing the query metadata, I hopped around a lot and missed an important aspect. This made me confused and took some time to debug. So I wanted to make this mistake immortal by writing a blog post about it.


Every query in the system has metadata that consists of selected satellites, seasons and labels. Every chunk of metadata is stored in a set. The application fetches the state of metadata from the corresponding sets and does whatever it does. Now, I have to write a query history feature, where for each query the metadata is stored and can be used to rerun the query again. Whenever user does a query, I set the metadata of the query in the map like below. My actual implementation was way more complex but the code snippets below convey the basic idea. Also I normally use Typescript but for the sake of simplicity, I will use Javascript in the code snippets.



let set = new Set([1,2,3])
const map = new Map()
map.set("key1", set)
set.add(4)
map.get("key1") // {1,2,3,4}

The problem with the code above is that the reference to the set is stored in the map, not the value of the set. Thus, for every query, the same reference is stored, and they all point to the same object. So, when the values inside the set change, we loose the snapshot of the state of the set for the past queries that we thought we have stored. To avoid this, one must copy the set like below and set it in the map. Doing that we create a new value and store its reference in the map. When we change the original set, we do not loose the stored value.


let set = new Set([1,2,3])
const map = new Map()
map.set("key1", new Set(set))
set.add(4)
map.get("key1") // {1,2,3}

Wait! What about the code snippet below? If we are storing the reference to the set in the map, when we change the value that the reference is pointing at, should not the value that we get from the map also change?


let set = new Set([1,2,3])
const map = new Map()
map.set("key1", set)
set = new Set([1,2,3,4])
map.get("key1") // {1,2,3}

This reveals that we put a copy of the reference in the map, and not the reference itself. Thus, when we point the original reference to another value, the reference copies do not change and still point to the old value. Check out the drawing below.


Reference vs value explanation.

The gist is when setting objects in the map, set clones of these objects. You have seen above how to clone a set. If you are setting an arbitrary object in a map, you can use structuredClone() to create a clone. structuredClone() will create a deep clone of the object. On the other hand, the spread operator or Object.assign will create shallow copies. Support for structuredClone() in Node.js was added in version v17.0.0.

To sum up, this process was more complex than the example that I have given above and required more thought put into the process. Not keeping the point that I mentioned above in mind caused some debugging for me and at some point I felt like this.



Anyways, I reached the happy ending and fixed my problem and learned new stuff along the way. The title of this post is inspired by the lightning talk of Gary Bernhardt from CodeMash 2012. It is a great short talk that you HAVE TO watch. I also like and code in Python. So here is another great post by Robert Heaton that explains pass-by-reference vs pass-by-value in Python.