Tell me more ×
Programmers Stack Exchange is a question and answer site for professional programmers interested in conceptual questions about software development. It's 100% free, no registration required.

I was involved in a programming discussion today where I made some statements that basically assumed axiomatically that circular references (between modules, classes, whatever) are generally bad. Once I got through with my pitch, my coworker asked, "what's wrong with circular references?"

I've got strong feelings on this, but it's hard for me to verbalize concisely and concretely. Any explanation that I may come up with tends to rely on other items that I too consider axioms ("can't use in isolation, so can't test", "unknown/undefined behavior as state mutates in the participating objects", etc.), but I'd love to hear a concise reason for why circular references are bad that don't take the kinds of leaps of faith that my own brain does, having spent many hours over the years untangling them to understand, fix, and extend various bits of code.

Edit: I am not asking about homogenous circular references, like those in a doubly-linked list or pointer-to-parent. This question is really asking about "larger scope" circular references, like libA calling libB which calls back to libA. Substitute 'module' for 'lib' if you like. Thanks for all of the answers so far!

share|improve this question
18  
I clicked on this link hoping for some witty remark on circular references, and found none. For those that are interested, I found one here: programmers.stackexchange.com/q/11856/175 – makerofthings7 Oct 16 '10 at 1:03
@makerofthings7 Very witty indeed, sir. Very witty indeed. – RonLugge Aug 17 '12 at 23:42

10 Answers

up vote 28 down vote accepted

There are a great many things wrong with circular references:

  • Circular class references create high coupling; both classes must be recompiled every time either of them is changed.

  • Circular assembly references prevent static linking, because B depends on A but A cannot be assembled until B is complete.

  • Circular object references can crash naïve recursive algorithms (such as serializers, visitors and pretty-printers) with stack overflows. The more advanced algorithms will have cycle detection and will merely fail with a more descriptive exception/error message.

  • Circular object references also make dependency injection impossible, significantly reducing the testability of your system.

  • Objects with a very large number of circular references are often God Objects. Even if they are not, they have a tendency to lead to Spaghetti Code.

  • Circular entity references (especially in databases, but also in domain models) prevent the use of non-nullability constraints, which may eventually lead to data corruption or at least inconsistency.

  • Circular references in general are simply confusing and drastically increase the cognitive load when attempting to understand how a program functions.

Please, think of the children; avoid circular references whenever you can.

share|improve this answer
I particularly appreciate the last point, "cognitive load" is something that I am very conscious of but never had a great concise term for it. – dash-tom-bang Oct 14 '10 at 16:40
Good answer. It would be better if you said something about testing. If modules A and B are mutually dependent, they must be tested together. This means they are not really separate modules; together they are one broken module. – kevin cline Oct 29 '12 at 18:35
@kevincline: While that's a true statement, I haven't been able to convince myself that it's germane to circular references; even a one-way module dependency implies that at least one module must be tested together with another (dependent) module, unless some type of abstraction is used - in which case, the same abstraction would make the circular reference testable. If I'm overlooking something, can you clarify with a specific example? – Aaronaught Oct 29 '12 at 22:46
@Aaronaught Circular referenced objects are really hard to serialize. Add this one too :). – AnyOne Oct 30 '12 at 10:56
@Aaronaught: if A depends on B, then you can test B alone. If they are mutually dependent, you can't test either alone, so they aren't separate in any useful way. – kevin cline Oct 30 '12 at 15:23
show 1 more comment

A circular reference is twice the coupling of a non-circular reference.

If Foo knows about Bar, and Bar knows about Foo, you have two things that need changing (when the requirement comes that Foos and Bars must no longer know about each other). If Foo knows about Bar, but a Bar doesn't know about Foo, you can change Foo without touching Bar.

Cyclical references can also cause bootstrapping problems, at least in environments that last for a long time (deployed services, image-based development environments), where Foo depends on Bar working in order to load, but Bar also depends on Foo working in order to load.

share|improve this answer

When you tie two bits of code together, you effectively have one large piece of code. The difficulty of maintaining a bit of code is at least the square of its size, and possibly higher.

People often look at single class (/function/file/etc.) complexity and forget that you really should be considering the complexity of the smallest separable (encapsulatable) unit. Having a circular dependency increases the size of that unit, possibly invisibly (until you start trying to change file 1 and realize that also requires changes in files 2-127).

share|improve this answer

Is like the Chicken or the Egg problem.

There are many cases in which circular reference are inevitable and are useful but, for example, in the following case it doesn't work:

Project A depends on project B and B depends on A. A needs to be compiled to be used in B which requires B to be compiled before A which requires B to be compiled before A which ...

share|improve this answer

Hmm... that depends on what you mean by circular dependence, because there are actually some circular dependencies which I think are very beneficial.

Consider an XML DOM -- it makes sense for every node to have a reference to their parent, and for every parent to have a list of it's children.

share|improve this answer
wouldn't that be a tree? – Conrad Frix Oct 14 '10 at 1:31
@Conrad: I suppose it could be thought of as a tree, yes. Why? – Billy ONeal Oct 14 '10 at 2:14
I don't think of tree's as circular because you can navigate down its children and will terminate (regardless of the parent reference). Unless a node had a child that was also a ancestor which in my mind makes it a graph and not a tree. – Conrad Frix Oct 14 '10 at 15:21
A circular reference would be if one of the children of a node looped back to an ancestor. – Matt Olenik Oct 14 '10 at 17:04

They may be bad not by themselves but as an indicator of a possible poor design. If Foo depends on Bar and Bar depends on Foo, it is justified to question why they are two instead of a unique FooBar.

share|improve this answer

In database terms, circular references with proper PK/FK relationships make it impossible to insert or delete data. If you can't delete from table a unless the record is gone from table ba and you can't delete from table b unless the record is gone form table A, you can't delete. Same with inserts. this is why many databases do not allow you to set up cascading updates or deletes if there is a circular reference becasue at some point it becomes not possible. Yes you can set up these kind of relationships with out the PK/Fk being formally declared but then you will (100% of the time in my experience) have data integrity problems. That's just bad design.

share|improve this answer

Garbage collectors tend to have trouble cleaning them up, because each object is being referenced by another.

share|improve this answer
8  
Hmm.. any garbage collector tripped up by this isn't a true garbage collector. – Billy ONeal Oct 14 '10 at 0:37
9  
I don't know of any modern garbage collector which would have problems with circular references. Circular references are a problem if you're using reference counts, but most garbage collectors are tracing style (where you start with the list of known references and follow them to find all others, collecting everything else). – Dean Harding Oct 14 '10 at 0:40
3  
See sct.ethz.ch/teaching/ws2005/semspecver/slides/takano.pdf who explains the drawbacks to various types of garbage collectors -- if take mark and sweep and start optimizing it to reduce the long pause times (e.g. creating generations), you start to have problems with circular structures (when circular objects are in different generations). If you take reference counts and start fixing the circular reference problem, you end up introducing the long pause times are characteristic of mark and sweep. – Ken Bloom Oct 14 '10 at 13:45
If a garbage collector looked at Foo and deallocated its memory which in this example references Bar it should handle the removal of Bar. Thus at this point there is no need for garbage collector to go ahead and remove bar because it already did. Or vice versa, if it removes Bar which references Foo it shuold remove Foo too and thus it will not need to go remove Foo because it did so when it removed Bar? Please correct me if I am wrong. – Chris Oct 14 '10 at 13:46
1  
In objective-c, circular references make it so the ref count doesn't hit zero when you release, which trips up the garbage collector. – DexterW Oct 14 '10 at 14:06
show 3 more comments

I'd answer that question with another question:

What situation can you give me where keeping a circular reference model is the best model for what you're trying to build?

From my experience, the best model will pretty much never involve circular references in the way I think you mean it. That being said, there are a lot of models where you use circular references all the time, it's just extremely basic. Parent -> Child relationships, any graph model, etc, but these are well known models and I think you're referring to something else entirely.

share|improve this answer
It MAY be that a circular linked list (single-linked or double-linked) would be an excellent data structure for the central event queue for a program that's supposed to "never stop" (stick the important N things on the queue, with a "do not delete" flag set, then simply traverse the queue until empty; when new tasks (transient or permanent) are needed, stick them in a suitable place on the queue; whenever you serve an even without the "do not delete" flag, do it then take it off the queue). – Vatine Oct 14 '10 at 14:11

The term "circular reference" is somewhat vague, your question needs some context to answer. For example, in a doubly linked list there are references (pointers) back and forth, but it is in no way harmful.

But another meaning (under .NET) is when you reference an assembly with yours. In this case, a "circular reference" breaks compilation.

share|improve this answer
When one says circular reference, one typically means that following a chain of pointers (e.g. myobj->next->next->next) will eventually lead back to the starting point. Circular implies that there is no "terminating condition" to signal that you've reached the end. Which is quite different from a doubly linked list or a tree. – dash-tom-bang Mar 20 '12 at 0:55

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.