The Bouncer

An easy Python interview practice problem on DataDriven. Write the Python and run it against test cases, with instant feedback.

Domain
Python
Difficulty
easy
Seniority
L3
Asked in
Citadel

Problem

Door access operations arrive in order as [action, name] pairs, where action is grant, revoke, or check and a guest's name is matched regardless of capitalization. Every check yields True if that guest holds access at that moment and False otherwise; return those outcomes in order.

Every door has a guest list.

Example 1

Input:

ops = [["grant","Ana"],["check","ana"],["grant","Ana"],["revoke","Ana"],["check","Ana"],["check","Ben"]]

Output:

[true,false,false]

Example 2

Input:

ops = [["revoke","Cara"],["check","Cara"],["grant","Cara"],["check","Cara"]]

Output:

[false,true]

Worked solution and explanation

What this problem really is

Peel back the velvet rope and this is live membership tracking over a stream. Keep a running picture of exactly who is allowed in right now, and answer each check against that picture. The skill is making grant idempotent and revoke safe. Anyone can loop the operations; what separates candidates is two decisions a naive list gets wrong. Grant the same guest twice as plain appends and you leave two copies, so one revoke removes only one and the guest still reads as allowed. Revoke a guest who was never added and list.remove throws instead of doing nothing. Model current access as a set and both problems vanish: adding is idempotent, discarding is a safe no-op, and a check is a single membership test.


Break down the requirements

Step 1: Hold current access as a set

Keep one set of the guests allowed in right now. Adding a guest who is already there changes nothing, so a second grant is harmless, and testing membership is O(1). That single choice gives you an idempotent grant for free.

Step 2: Walk the operations in order

Read each [action, name] pair as it arrives. 'grant' adds the name and 'revoke' discards it. Use discard, not remove, so a guest who was never granted is simply ignored rather than raising. 'check' appends True or False depending on whether the name is currently in granted. Order matters: a check sees only the grants and revokes that came before it.

Step 3: Normalize the name before matching

Lowercase each name with lower() before it touches the set, so 'Ana', 'ana', and 'ANA' collapse to one guest instead of three phantom entries. Do it once, at the point you read the name, and every add, discard, and check stays consistent.


The solution

Set-backed live access tracker
def door_checks(ops: list[list]) -> list:
    granted = set()
    results = []
    for op in ops:
        action = op[0]
        name = op[1].lower()
        if action == "grant":
            granted.add(name)
        elif action == "revoke":
            granted.discard(name)
        else:  # check
            results.append(name in granted)
    return results
List with append and remove (wrong)
Grant calls append and revoke calls list.remove. Duplicate grants leave stragglers that a single revoke cannot clear, revoking an absent guest throws, and every check scans the list. It is both incorrect and slow.
Set with add and discard (right)
Grant calls add, revoke calls discard, and check tests membership with in. Duplicate grants are absorbed, absent revokes are silent no-ops, and every operation is O(1). It is correct and fast.

Common follow-up questions