Hierarchical Access Control for an Advertising Platform
Company: Pinterest
Role: Machine Learning Engineer
Category: Coding & Algorithms
Difficulty: medium
Interview Round: Onsite
Quick Answer: This question tests practical application of graph traversal and access control system design using a directed acyclic group hierarchy. It evaluates a candidate's ability to model inheritance-based permissions, choose appropriate data structures for efficient ancestor lookups, and handle grant/revoke semantics under read-heavy workloads — core competencies in coding and algorithms interviews for roles requiring systems thinking.
Constraints
- Group and advertiser identifiers are strings.
- The group hierarchy is a DAG: a group may have multiple parents, but there are no cycles.
- grant_access is idempotent; revoke_access on a non-existent grant is a no-op.
- revoke_access only removes a grant made directly on the named group; inherited access via ancestors is unaffected.
- Access is inherited downward only — a grant on a group never confers access to its ancestors.
- Queries (check_access) far outnumber writes; a group may have hundreds of ancestors.
Examples
Input: ([['root','regionA'],['root','regionB'],['regionA','teamX'],['regionA','teamY']], [['grant_access','alice','regionA'],['check_access','alice','teamX'],['check_access','alice','regionB'],['check_access','alice','root'],['revoke_access','alice','regionA'],['check_access','alice','teamX']])
Expected Output: [True, False, False, False]
Explanation: alice is granted regionA. teamX is a descendant of regionA -> true. regionB is not under regionA -> false. root is an ancestor, grants don't propagate upward -> false. After revoking regionA, teamX is no longer reachable -> false.
Input: ([], [['check_access','bob','x']])
Expected Output: [False]
Explanation: Empty hierarchy and no grants: bob has no access to x.
Input: ([['a','b'],['b','c'],['x','c']], [['grant_access','u','a'],['check_access','u','c'],['grant_access','u','a'],['check_access','u','c'],['revoke_access','u','a'],['check_access','u','c'],['grant_access','u','x'],['check_access','u','c']])
Expected Output: [True, True, False, True]
Explanation: c has two ancestor paths (via a->b and via x). Grant on a reaches c -> true; the second grant on a is idempotent -> still true. After revoking a, c is no longer reachable -> false. Granting x (the other parent of c) restores access -> true.
Input: ([['g','h']], [['revoke_access','z','g'],['check_access','z','g'],['grant_access','z','g'],['check_access','z','g'],['check_access','z','h']])
Expected Output: [False, True, True]
Explanation: Revoking a non-existent grant is a no-op, so the first check is false. After granting g, z can access g and its descendant h.
Input: ([['root','a'],['a','b'],['b','c']], [['grant_access','p','b'],['check_access','p','c'],['check_access','p','root'],['check_access','p','a']])
Expected Output: [True, False, False]
Explanation: Grant on b reaches descendant c -> true, but does not reach ancestors root or a -> false. Confirms downward-only inheritance through a multi-level chain.
Hints
- Store grants as advertiser -> set of directly-granted groups. Do not eagerly expand grants to all descendants; that makes revoke and large sub-trees expensive.
- For check_access, walk UPWARD from the queried group through its ancestors (the group itself first) and return true the moment you hit a group that was directly granted to that advertiser.
- Because the hierarchy is a DAG (multiple parents), use a visited set during the upward walk so you don't revisit a shared ancestor.
- revoke_access must only remove the direct grant on the named group — a simple set.discard — never touch ancestor grants.