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

Set-based permission tracking with arity-aware dispatch
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 results

Common follow-up questions