Who Gets In
A medium Python interview practice problem on DataDriven. Write the Python and run it against test cases, with instant feedback.
- Domain
- Python
- Difficulty
- medium
- Seniority
- L5
- Asked in
- Citadel
Problem
A permission service replays a stream of access changes: grant and revoke add or drop a single permission for one user, check asks whether a user currently holds a given permission, and update_config hands over a whole user-to-permissions mapping that replaces every permission on record. Most operations name an action, a user, and a permission, but update_config carries only the action and the mapping. Return the boolean answer to each check in order, and treat a user you have never seen as holding nothing.
When the roster changes, does access follow?
Example
Input:
operations = [["grant","alice","read"],["grant","alice","write"],["check","alice","read"],["check","alice","delete"],["revoke","alice","read"],["check","alice","read"],["update_config",{"bob":["read","write"]}],["check","alice","write"],["check","bob","read"]]Output:
[true,false,false,false,true]
Worked solution and explanation
What this problem really is
This is variable-arity command dispatch hiding a state-consistency trap. Everyone can maintain a dict of permission sets and branch on grant/revoke/check. What separates candidates is noticing that update_config is only two elements long, so a loop that reaches for op[2] on every operation crashes with IndexError the moment the first config push arrives. Dispatch on the action first, unpack second.
Break down the requirements
Step 1: Maintain a set of permissions per user
Keep a dict mapping each user to a set of their current permissions. Sets make grant idempotent, revoke idempotent, and check an O(1) membership test.
Step 2: Dispatch on the action before unpacking
Operations are not all the same length, so read op[0] FIRST. The 'update_config' op has only 2 elements (action plus a dict), so reading op[2] on it would raise IndexError. Handle it before touching user/permission fields.
Step 3: Process each operation in order
grant adds to the set, revoke discards from the set (never remove, which throws on a second revoke), check tests membership (False if the user is unknown), and update_config rebuilds the entire permissions dict from the config, converting each permission list to a set.
Step 4: Collect check results
Append the boolean from every check to a results list and return it at the end, in operation order.
The solution
def permissions_manager(operations):
permissions = {}
results = []
for op in operations:
action = op[0]
if action == 'update_config':
config = op[1]
permissions = {u: set(perms) for u, perms in config.items()}
continue
user = op[1]
perm = op[2]
if action == 'grant':
permissions.setdefault(user, set()).add(perm)
elif action == 'revoke':
permissions.setdefault(user, set()).discard(perm)
elif action == 'check':
results.append(perm in permissions.get(user, set()))
return resultsCommon follow-up questions
- What if permissions should support hierarchical inheritance? (Tests building a permission tree where granting 'admin' implies 'read' and 'write'.)
- How would you change update_config to merge rather than replace? (Tests merging the config into existing permissions instead of replacing wholesale.)
- What if operations arrive concurrently from multiple threads? (Tests knowledge of thread-safe data structures or locking strategies.)
- How would you audit the permission history? (Tests appending each operation to an audit log alongside the state change.)