# Relationship State Resolution

- **Pattern:** `ab-000762` (`community-social-engagement.follow-graph.relationship-state-resolution`)
- **Severity:** medium
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-000762
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-000762)

## Why it matters

Resolving follow status with a separate query per user card is a textbook N+1 problem. A feed page showing 50 user cards fires 50 individual `isFollowing` database calls — each a round-trip that adds 5–20ms of latency in a cloud deployment. At 100ms/page the cumulative cost is 5 seconds of blocked rendering. Under ISO 25010:2011 performance-efficiency, this is a structural scalability defect: response time degrades linearly with the number of items displayed, making any growth in user density directly punishing.

## Severity rationale

Medium because the performance impact is proportional to page size and grows predictably, but it degrades user experience gradually rather than causing immediate failures.

## Remediation

Preload follow status alongside the user list in a single query, or use DataLoader to batch all `isFollowing` checks for a given page render into one database round-trip.

```ts
// lib/db.ts — preload approach
export async function getUsersWithFollowStatus(viewerId: string, limit: number) {
  const users = await db.user.findMany({
    take: limit,
    select: {
      id: true,
      name: true,
      avatar: true,
      followers: {
        where: { followerId: viewerId },
        select: { id: true }
      }
    }
  })

  return users.map(u => ({
    ...u,
    isFollowing: u.followers.length > 0,
    followers: undefined
  }))
}
```

This collapses N+1 queries into a single JOIN, regardless of how many user cards are rendered.

## Detection

- **ID:** `relationship-state-resolution`
- **Severity:** `medium`
- **What to look for:** Enumerate all relevant files and Examine how the UI determines whether the current user follows another user. Look for queries in profile pages or user cards. Identify if there's an N+1 problem: does the component query `isFollowing()` for every user card displayed, or is there batch loading (DataLoader pattern) or a single optimized query?
- **Pass criteria:** At least 1 implementation must be present. Relationship state is resolved efficiently. User cards in lists do not trigger individual follow-check queries. Either: (a) follow status is preloaded with the user list, (b) DataLoader batches queries, or (c) a single JOIN query fetches all follow status at once.
- **Fail criteria:** Each user card triggers a separate query to check follow status (N+1 problem), or follow status requires additional round-trips to the server.
- **Skip (N/A) when:** Follow relationships are not displayed in the UI.
- **Detail on fail:** `"User list page makes a separate API call to /api/users/[id]/is-following for each user card — will load slowly with 50+ users"`
- **Remediation:** Batch load or preload relationship state:

  ```ts
  // Option 1: Preload in the list query
  export async function getUsers(limit: number, viewerId: string) {
    const users = await db.user.findMany({
      take: limit,
      select: {
        id: true,
        name: true,
        followers: {
          where: { followerId: viewerId },
          select: { id: true }
        }
      }
    })

    return users.map(u => ({
      ...u,
      isFollowing: u.followers.length > 0
    }))
  }

  // Option 2: DataLoader to batch follow checks
  import DataLoader from 'dataloader'

  const followLoader = new DataLoader(async (userIds: string[]) => {
    const follows = await db.follow.findMany({
      where: { followerId: viewerId, followedId: { in: userIds } }
    })
    const followMap = new Map(follows.map(f => [f.followedId, true]))
    return userIds.map(id => followMap.get(id) || false)
  })
  ```

## External references

- iso-25010 performance-efficiency

Taxons: performance, cost-efficiency

HTML version: https://auditbuffet.com/patterns/ab-000762
