Time-Travel Debugger in JavaScript
Imagine you're debugging a complex application where state changes happen rapidly and are difficult to trace. A "time-travel debugger" allows you to step backward and forward through the application's state history, inspecting variables and understanding how the application arrived at its current state. This challenge asks you to build a simplified version of such a debugger, focusing on tracking and replaying state changes.
Problem Description
You are tasked with creating a TimeTravelDebugger class in JavaScript. This class will track the state of an application over time, allowing you to "travel" to different points in its history. The debugger should be able to:
- Record State: Accept a function
appUpdatethat represents a single update to the application's state. Each timerecordStateis called withappUpdate, the debugger should executeappUpdateand store the resulting state along with the previous state. - Travel Through Time: Provide methods to move forward (
nextState()) and backward (previousState()) through the recorded states. - Inspect Current State: Offer a method to retrieve the current state (
getCurrentState()). - Reset: Provide a method to clear the history and start fresh.
The appUpdate function should accept the previous state as its argument and return the new state. The debugger should maintain a history of states, allowing for both forward and backward traversal.
Key Requirements:
- The debugger must handle cases where there are no previous states (at the beginning of the history) or no future states (at the end of the history).
- The debugger should not modify the original
appUpdatefunction. - The debugger should store the result of
appUpdateas the state, not the function itself.
Expected Behavior:
recordStateshould execute the provided function and store the new state.nextStateshould move to the next state in the history, if available.previousStateshould move to the previous state in the history, if available.getCurrentStateshould return the current state.resetshould clear the history.
Edge Cases to Consider:
- Calling
nextState()orpreviousState()when at the beginning or end of the history. appUpdatefunctions that return the same state repeatedly.appUpdatefunctions that throw errors. (The debugger should gracefully handle these, potentially by stopping state recording.)
Examples
Example 1:
Input:
const appUpdate = (prevState) => prevState + 1;
const debugger = new TimeTravelDebugger(appUpdate);
debugger.recordState();
debugger.recordState();
debugger.recordState();
Output:
debugger.getCurrentState() // 3
debugger.nextState();
debugger.getCurrentState() // 4
debugger.previousState();
debugger.getCurrentState() // 3
Explanation: The appUpdate function increments the previous state. The debugger records three states (0, 1, 2, 3). nextState() moves to 4, and previousState() moves back to 3.
Example 2:
Input:
const appUpdate = (prevState) => {
if (prevState < 5) {
return prevState + 1;
} else {
return prevState;
}
};
const debugger = new TimeTravelDebugger(appUpdate);
debugger.recordState();
debugger.recordState();
debugger.recordState();
debugger.recordState();
debugger.recordState();
Output:
debugger.getCurrentState() // 5
debugger.nextState();
debugger.getCurrentState() // 5
debugger.previousState();
debugger.previousState();
debugger.getCurrentState() // 4
Explanation: The appUpdate function increments the state until it reaches 5, then stops. The debugger records states 0, 1, 2, 3, 4, 5. After reaching 5, nextState() has no effect.
Example 3: (Edge Case)
Input:
const appUpdate = (prevState) => prevState; // Returns the same state
const debugger = new TimeTravelDebugger(appUpdate);
debugger.recordState();
debugger.recordState();
Output:
debugger.getCurrentState() // 0
debugger.nextState();
debugger.getCurrentState() // 0
debugger.previousState();
debugger.getCurrentState() // 0
Explanation: The appUpdate function always returns the same state. The debugger records two identical states. Moving forward or backward has no effect.
Constraints
- The
appUpdatefunction should be a pure function (no side effects). - The debugger should maintain a maximum history of 100 states to prevent excessive memory usage.
- The
appUpdatefunction should not take more than 100ms to execute. (This is a design consideration for theappUpdatefunction, not a constraint on the debugger itself, but it's important to be aware of). - The debugger should handle errors thrown by
appUpdategracefully, preventing the debugger from crashing.
Notes
- Consider using an array to store the state history.
- Think about how to handle the initial state (before any
recordStatecalls). You can either require an initial state to be passed to the constructor or assume a default initial state (e.g.,nullorundefined). - Error handling is important. Consider what should happen if
appUpdatethrows an error. Should the debugger stop recording states? Should it continue? - This is a simplified debugger. A real debugger would have many more features, such as breakpoints, variable inspection, and more sophisticated stepping capabilities. Focus on the core functionality of state tracking and time travel.