You’re building a fintech mobile web app (millions of DAUs) where login reliability directly impacts revenue and regulatory access controls. The UI is implemented in React, but the team keeps shipping subtle bugs: users get stuck in “loading”, retries don’t reset error banners, and rapid clicks trigger duplicate requests.
To make the behavior testable, you decide to model the login flow as a deterministic state machine and write a pure function that simulates the UI state transitions given a sequence of events.
Implement a function:
events: List[str] — a chronological list of events.max_retries: int — maximum number of retries allowed after failures/errors.(final_state, attempts, message) where:
final_state is one of: "IDLE", "LOADING", "SUCCESS", "FAILURE", "ERROR", "LOCKED"attempts is the number of login attempts startedmessage is a user-facing string describing the final outcomeEach event is one of:
"SUBMIT" — user clicks “Log in”"RESOLVE_SUCCESS" — server responds with success for the in-flight attempt"RESOLVE_FAILURE" — server responds with invalid credentials for the in-flight attempt"RESOLVE_ERROR" — network/5xx error for the in-flight attempt"RETRY" — user clicks “Try again”"CANCEL" — user cancels the in-flight attempt (e.g., navigates away)IDLE with attempts = 0 and retries_used = 0.SUBMIT in IDLE, FAILURE, or ERROR starts a new attempt: state → LOADING, attempts += 1.LOADING, only RESOLVE_SUCCESS, RESOLVE_FAILURE, RESOLVE_ERROR, and CANCEL affect state:
RESOLVE_SUCCESS: state → SUCCESSRESOLVE_FAILURE: state → FAILURERESOLVE_ERROR: state → ERRORCANCEL: state → IDLE (attempt is considered started already; do not decrement attempts)RETRY is valid only in FAILURE or ERROR:
retries_used < max_retries, then retries_used += 1 and state → LOADING, attempts += 1LOCKEDSUCCESS or LOCKED, all subsequent events are ignored.Input: events = ["SUBMIT", "RESOLVE_FAILURE", "RETRY", "RESOLVE_SUCCESS"], max_retries = 2
Output: (SUCCESS, 2, "Logged in")
Explanation: First attempt fails → user retries (allowed) → second attempt succeeds.
Input: events = ["SUBMIT", "RESOLVE_ERROR", "RETRY", "RESOLVE_ERROR", "RETRY"], max_retries = 1
Output: (LOCKED, 2, "Too many retries")
Explanation: One retry is allowed. After the second error, the next RETRY exceeds the limit → locked.
attempts counts every time you enter LOADING due to SUBMIT or RETRY.RESOLVE_* events when not in LOADING are ignored (stale responses).