Common causes
1. Parent and child objects pointing at each other
Trees with back-references (a node with parent, a team with a lead whose team points back) are circular by design. Leave the back-reference out with a replacer.
const json = JSON.stringify(team);const json = JSON.stringify(team, (key, value) => (key === 'team' ? undefined : value));2. Logging framework objects such as req, res or an Axios error
Express requests, Node sockets and Axios errors link to each other (socket.parser.socket, error.request.res). Log the fields you need instead of the whole object.
logger.info(JSON.stringify(error));logger.info(
JSON.stringify({
message: error.message,
status: error.response?.status,
url: error.config?.url,
}),
);3. DOM nodes, events or React refs in state
A DOM element references its document, which references the element again. Store the values you need (an id, the input value) rather than the node or event.
localStorage.setItem('lastClick', JSON.stringify(event));localStorage.setItem('lastClick', JSON.stringify({ id: event.target.id, x: event.clientX, y: event.clientY }));4. ORM or Mongoose documents with relations loaded
Entities with both sides of a relation populated form cycles, and model instances carry internal state. Convert to plain data first, for example with doc.toObject() or a DTO that lists the fields to send.
res.send(JSON.stringify(order));res.send(JSON.stringify({ id: order.id, total: order.total, customerId: order.customer.id }));5. Generic fallback: drop repeated references
For debugging output, a replacer that remembers seen objects in a WeakSet prints each object once and marks the repeats. It also hides legitimately shared objects, so use it for logs, not for data you will parse again.
console.log(JSON.stringify(state));const seen = new WeakSet();
console.log(
JSON.stringify(state, (key, value) => {
if (typeof value === 'object' && value !== null) {
if (seen.has(value)) return '[Circular]';
seen.add(value);
}
return value;
}),
);Frequently asked questions
How do I read the "--> starting at object" lines?
They trace the loop: the object where it starts, then each property followed, and finally the property that points back to the start. The constructor names (Object, Socket, HTMLDivElement) tell you which kind of object is involved.
Does console.log have the same problem?
No. console.log and Node’s util.inspect detect cycles and print [Circular *1] instead of throwing. Only JSON serialization requires a tree.
Can JSON represent circular data at all?
Not directly. Formats such as JSON Pointer references or libraries like flatted encode cycles as references, but both sides must agree on the convention.