2025, Nov 08 05:00
Correctly compute mutual friends in Django with a self-referential non-symmetrical ManyToMany follow model
Learn how to compute true mutual friends in Django by intersecting follow sets in a self-referential, non-symmetrical ManyToMany. Avoid the 'who I follow' myth
When you already have following/followers in place, the next natural step is to surface mutual friends — essentially friends of friends — between the current user and other profiles. The pitfall here is subtle: it is easy to fetch “people I follow” and mistakenly label that as “mutual,” while never intersecting with the other user’s network.
Problem in context
The data model is a self-referential ManyToMany for following, explicitly non-symmetrical so “A follows B” does not imply “B follows A.”
class MemberProfile(models.Model):
account = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, blank=True, null=True)
picture = models.ImageField(upload_to="UploadedProfilePicture/", default="ProfileAvatar/avatar.png", blank=True)
subscribes = models.ManyToManyField(
"MemberProfile",
symmetrical=False,
related_name="subscribers",
blank=True,
)The view attempts to compute “mutuals,” but ends up returning only the current user’s outgoing follows.
def show_following(request):
title_text = "Following"
# Other profiles (excluding the current user)
others = MemberProfile.objects.exclude(Q(account=request.user))
# Current user profile
me = MemberProfile.objects.get(account=request.user)
# Mistake: this is just "who I follow"
my_follow_ids = me.subscribes.values_list("pk", flat=True)
supposed_mutuals = MemberProfile.objects.filter(pk__in=my_follow_ids)What actually goes wrong
The variable that is meant to represent mutual friends is populated with the current user’s follows only. There is no comparison with the other user’s follow set, so there is no intersection and therefore no true “mutual” computation. Given the non-symmetrical setup, a mutual relationship here means profiles that both the current user and a specific other profile follow. Without checking the other profile’s follows, the result will never be mutuals; it is simply a copy of the current user’s outgoing connections.
Working approach
The ManyToMany field provides everything needed. Take the current user’s follow set once, and then, for each candidate profile, check which of those are also followed by that profile. This yields the shared subset — the mutual friends for that pair.
# views.py
def show_following(request):
title_text = "Following"
# Everyone except me
others = MemberProfile.objects.exclude(Q(account=request.user))
# My profile and my outgoing follows
me = MemberProfile.objects.get(account=request.user)
my_follow_ids = me.subscribes.values_list("pk", flat=True)
# Candidates to check for overlap (my follows)
overlap_pool = MemberProfile.objects.filter(pk__in=my_follow_ids)# template (example)
{% for person in others %}
<p>{{ person.account }}</p>
{% for common in overlap_pool %}
{% if common in person.subscribes.all %}
<p>{{ common.account.username }}</p>
{% endif %}
{% endfor %}
{% endfor %}This logic mirrors the intent precisely: for each person, display those profiles that appear in both sets — your follows and their follows.
Why this matters
Mislabeling “who I follow” as “mutual friends” breaks user expectations and undermines features that rely on accurate social graphs. With a non-symmetrical following model, intersections must be computed explicitly per pair to reflect real mutuals.
Takeaways
With a self-referential non-symmetrical ManyToMany, mutual friends emerge only from intersecting two follow sets: the current user’s and the other profile’s. Fetch your own follows once, iterate other profiles, and for each of them display those who exist in both sets. Keep the list of profiles excluded from yourself, and rely on the existing following/followers relationships without introducing additional assumptions.
The article is based on a question from StackOverflow by user30880337 and an answer by user30880337.