Given an object with a cycle, remove the cycle or circular reference from it.

Input:
const List = function(val){
this.next = null;
this.val = val;
};
const item1 = new List(10);
const item2 = new List(20);
const item3 = new List(30);
item1.next = item2;
item2.next = item3;
item3.next = item1;
// this form a cycle, if you console.log this you will see a circular object,
// like, item1 -> item2 -> item3 -> item1 -> so on.
Output:
// removes cycle
// item1 -> item2 -> item3
If you see the above example, we have created a list object, that accepts a value and pointer to the next item in the list, similar to a linked list, and using this we have created the circular object.
We have to create a function that will break this cycle, in this example to break the cycle we will have to delete the next pointer of the item3.
There are two places where this cycle removal can take place:
We will see both approaches.
We can use WeakSet which is used to store only unique object references and detect if the given object was previously detected or not, if it was detected then delete it.
This cycle remove always takes place inplace.
const removeCycle = (obj) => {
// set to store object references
const set = new WeakSet([obj]);
// recursively detects and deletes circular references
(function iterateObj(obj) {
for (let key in obj) {
// ensure the property belongs directly to the object (not inherited)
if (obj.hasOwnProperty(key)) {
if (typeof obj[key] === 'object') {
// if this object reference already exists, it's a cycle
if (set.has(obj[key])) {
delete obj[key];
} else {
// store the new object reference
set.add(obj[key]);
// recursively check nested objects
iterateObj(obj[key]);
}
}
}
}
})(obj);
}
Input:
const List = function(val){
this.next = null;
this.val = val;
};
const item1 = new List(10);
const item2 = new List(20);
const item3 = new List(30);
item1.next = item2;
item2.next = item3;
item3.next = item1;
removeCycle(item1);
console.log(item1);
Output:
/*
{val: 10, next: {val: 20, next: {val: 30}}}
*/
JSON.stringify() while creating JSON.JSON.stringify(), accepts a replacer function that can be used to alter the value of the strinfication process.
We can use the same function to detect and remove the cycle from the object.
const getCircularReplacer = () => {
// Create a WeakSet to store object references
// WeakSet is used because:
// 1. It stores only objects (perfect for reference tracking)
// 2. It does not prevent garbage collection (no memory leaks)
// 3. It allows us to detect circular references safely
const seen = new WeakSet();
// Return a replacer function
// This function will be called by JSON.stringify
// for every key-value pair in the object
return (key, value) => {
// Check if the current value is an object
// Circular references can only occur in objects
// (typeof null === 'object', so we explicitly exclude null)
if (typeof value === 'object' && value !== null) {
// If the object reference is already in WeakSet,
// it means we have encountered the same object again,
// which indicates a circular reference
if (seen.has(value)) {
// Returning undefined tells JSON.stringify
// to omit this key from the final JSON output
return;
}
// If this object has not been seen before,
// store its reference in WeakSet
// so future occurrences can be detected as circular
seen.add(value);
}
// If the value is:
// - a primitive (string, number, boolean)
// - null
// - or a non-circular object
// then return it normally so JSON.stringify can process it
return value;
};
};