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
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
appendand revoke callslist.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 callsdiscard, and check tests membership within. 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
- The venue also wants a nightly roster of everyone currently allowed in. How would you produce it, sorted? (Tests turning the live set into an ordered output with `sorted()`.)
- Now each guest can be granted access to specific rooms, not the whole venue. How does your state change? (Tests moving from a flat set to a dict mapping each guest to a set of rooms.)
- Operations stream in faster than memory allows a full replay. Can you still answer each check in real time? (Tests recognizing that the running set is already an online structure that needs only current state, not full history.)