Friend Recommender
HardAI-assisted coding · AI-Assisted Feature Work
Friend Recommender
You've joined the growth-engineering team that owns friendrec, the service behind a social app's "People you may know" panel. It stores the friendship graph and suggests friends of friends. Support has a bug report, and three feature tickets are queued behind it.
Codebase
| File | Purpose |
|---|---|
friendrec/app.py | run() entrypoint and the operation dispatcher |
friendrec/service.py | FriendService: validation, caching, the public operations |
friendrec/graph.py | SocialGraph: users and symmetric friendships |
friendrec/recommender.py | Candidate generation for suggestions |
friendrec/ranking.py | Ranking strategies (RANKERS registry) |
friendrec/cache.py | Per-user cache of recommendation results |
friendrec/pairs.py | Network-wide suggestions feed (stub, Part 4) |
friendrec/models.py | The User record, with its privacy flags |
friendrec/config.py | Constants (PRIVACY_FLAGS, ...) |
friendrec/demo.py | Deterministic demo-data seeder (known-good) |
tests/ | Visible tests (plain assert functions) |
How your code is run
The grader calls run(operations) from friendrec/app.py and compares the list of results with the expected one: one result per operation, in order. Errors are strings starting with error:, never exceptions. User ids are non-empty strings. Wherever this brief says "ascending" for ids, it means plain string comparison, so "u10" comes before "u2".
| Operation | Returns |
|---|---|
["add_user", id] | "ok". Errors: error: invalid user id (not a non-empty string), error: user exists |
["befriend", a, b] | "ok". Errors, checked in this order: error: unknown user 'x' (a before b), error: cannot befriend self, error: already friends |
["unfriend", a, b] | "ok". Errors: unknown user, error: not friends |
["friends", id] | the user's friends, ascending |
["mutuals", a, b] | friends of both users, ascending. A raw graph query |
["recommend", id, k] or ["recommend", id, k, strategy] | up to k suggested ids, best first. strategy defaults to "id" |
["seed_demo", n, per_user, seed] | {"users": n, "friendships": m}; builds a deterministic network u0..u{n-1} (used by load tests) |
A suggestion for user U is someone who is a friend of at least one of U's friends. Errors for recommend, in this order: error: unknown user 'x', error: invalid k (k must be an integer of at least 1), error: unknown strategy 'x'. Strategy "id" lists suggestions in ascending id order. A wrong number of arguments gives error: bad arguments for 'name', an unknown name error: unknown operation 'name', and anything that is not a non-empty list starting with a string error: malformed operation.
Worked example:
[["add_user", "ann"], ["add_user", "bob"], ["add_user", "cat"], ["add_user", "dan"],
["befriend", "ann", "bob"], ["befriend", "bob", "cat"], ["befriend", "bob", "dan"],
["recommend", "ann", 5]]returns "ok" eight times and then ["cat", "dan"].
Part 1 — Wrong and stale suggestions
Goal: Suggestions must be correct and always up to date.
Support reports: "the panel suggests people I'm already friends with, and sometimes myself", and "I added a friend, but my friends' panels didn't show them until much later".
Rules
- A suggestion is never the user themselves and never someone who is already their friend.
- After
befriendorunfriend, every laterrecommendreflects the new graph. That includes users other than the two involved. - Everything else keeps working as described above.
Example: with friendships ann-bob, ann-cat, bob-cat and cat-dan, ["recommend", "ann", 5] is ["dan"].
Part 2 — Rank by mutual friends
Goal: Offer a smarter ordering through the existing strategy argument.
Rules
- A new strategy
"mutual"orders suggestions by number of mutual friends (friends the user and the suggestion share), highest first. Equal counts are ordered by ascending id. The default strategy"id"is unchanged. - New operation
["explain", id, candidate]returns{"mutual": n, "via": [ids]}, whereviais the ascending list of the shared friends, ifcandidatewould be suggested toid, andnullotherwise (the candidate is the user, a friend, or shares no friend). Errors:error: unknown user 'x'(first argument checked first). - Unknown strategies still give
error: unknown strategy 'x'.
Example: ann is friends with bob, cat and dan. eve is friends with all three, fay and ivy with bob and cat, gus with dan. ["recommend", "ann", 10, "mutual"] is ["eve", "fay", "ivy", "gus"] (counts 3, 2, 2, 1), while "id" gives ["eve", "fay", "gus", "ivy"]. ["explain", "ann", "eve"] is {"mutual": 3, "via": ["bob", "cat", "dan"]}.
Part 3 — Blocks and privacy
Goal: Let users block each other and control how they appear in suggestions.
| Operation | Returns |
|---|---|
["block", blocker, target] | "ok". Errors in order: unknown user (blocker first), error: cannot block self, error: already blocked |
["unblock", blocker, target] | "ok". Errors: unknown user, error: not blocked |
["blocks", id] | ascending list of the users id has blocked. Error: unknown user |
["set_privacy", id, flag, value] | "ok". Errors in order: unknown user, error: unknown flag 'x', error: invalid value (value must be a boolean) |
The flags are in PRIVACY_FLAGS and both default to true: discoverable and friends_visible.
Rules
blockalso removes any friendship between the two users.unblockdoes not restore it.- While either user has blocked the other,
befriendfails witherror: blocked(after the unknown-user and self checks, beforealready friends), and each is hidden from the other: never suggested, andexplainreturnsnullfor the pair. - A user with
discoverableset tofalseis never suggested to anyone andexplainreturnsnullfor them as a candidate. Their own suggestions still work. - A user with
friends_visibleset tofalseis not used as a connection: they are not counted as a mutual friend for anyone, do not appear invia, and do not by themselves make someone a suggestion. Their own suggestions still work, andfriendsandmutualsignore this flag. - With no blocks and both flags
true, behaviour is exactly that of Parts 1 and 2. Changes to blocks and flags must show up in laterrecommendresults.
Example: with friendships ann-bob, bob-cat and bob-dan, after ["block", "ann", "cat"], ["recommend", "ann", 5] and ["recommend", "cat", 5] both return ["dan"].
Part 4 — Network-wide top pairs (FR-310)
Goal: Growth wants a report of the strongest introductions across the whole network. It must work on networks of thousands of users.
["top_pairs", k] returns up to k entries {"a": id, "b": id, "mutual": n} where a < b as strings, ordered by mutual descending, then a, then b ascending. Error: error: invalid k (same rule as recommend); a wrong number of arguments is error: bad arguments for 'top_pairs'.
Rules
- A pair is listed only if the two users are not friends, neither has blocked the other, both are
discoverable, and they have at least one mutual friend. mutualcounts mutual friends the same way asexplain: friends withfriends_visibleset tofalseare not counted.- The result must agree with
recommendandexplain, and with every change made through earlier operations. - The tests include networks of up to 8000 users; comparing every pair of users will time out.
Example: with friendships ann-bob, ann-cat, ann-dan, bob-eve, cat-eve and dan-eve, ["top_pairs", 2] is [{"a": "ann", "b": "eve", "mutual": 3}, {"a": "bob", "b": "cat", "mutual": 2}].
Notes
graph.py,demo.py,cache.pyandapp.pyare known-good, apart from what the parts ask you to extend. Bugs are in the logic reachable fromrecommend.- You may use the AI assistant freely. Later parts must not break earlier ones; the visible tests are a starting point, not the full suite.
- Completing Parts 1 to 3 is a strong result; Part 4 is a stretch.
Example 1
- Input:
- ann-bob, ann-cat, bob-cat, cat-dan are friends; ["recommend", "ann", 5]
- Output:
- ["dan"]
- Explanation
- Bob and Cat are already ann's friends and ann herself is never suggested; only dan is a friend of a friend who is not yet a friend.
Example 2
- Input:
- ann is friends with bob, cat, dan; eve is friends with all three, fay with bob and cat, ivy with bob and cat, gus with dan; ["recommend", "ann", 10, "mutual"]
- Output:
- ["eve", "fay", "ivy", "gus"]
- Explanation
- Ranked by number of mutual friends (3, 2, 2, 1); fay and ivy tie and are ordered by id.
Example 3
- Input:
- ann-bob, bob-cat, bob-dan are friends; ["block", "ann", "cat"], then ["recommend", "ann", 5] and ["recommend", "cat", 5]
- Output:
- "ok", ["dan"], ["dan"]
- Explanation
- Ann and cat no longer see each other, so each is only suggested dan.
Guidelines
- Python 3 standard library only
- User ids are strings; wherever order matters, ids are compared as plain strings ("u10" < "u2")
- Friendships are symmetric; a block is directional but hides the pair in both directions
- Every operation returns a value; errors are "error: ..." strings, never exceptions
- The whole suite, including networks of thousands of users, must run in a few seconds