1472. Design Browser History
Problem Description
You need to implement a browser history system that simulates the back and forward navigation functionality of a web browser with a single tab.
The browser starts on a homepage, and you can:
- Visit new URLs (which clears any forward history)
- Navigate backward through your browsing history
- Navigate forward through pages you've gone back from
The BrowserHistory
class requires implementing four methods:
-
Constructor
BrowserHistory(string homepage)
: Initializes the browser with a starting homepage. -
Visit
visit(string url)
: Navigate to a new URL from the current page. This action clears all forward history (any pages you could have gone forward to are lost). -
Back
back(int steps)
: Move backward in history by up tosteps
pages. If there are fewer thansteps
pages in the backward history, move back as far as possible. Returns the URL of the page you land on. -
Forward
forward(int steps)
: Move forward in history by up tosteps
pages. If there are fewer thansteps
pages in the forward history, move forward as far as possible. Returns the URL of the page you land on.
The solution uses two stacks to track navigation:
stk1
: Stores the current page and all pages in the backward historystk2
: Stores pages that can be reached by going forward (pages you've gone back from)
When visiting a new URL, it's added to stk1
and stk2
is cleared (forward history is lost). When going back, pages are moved from stk1
to stk2
. When going forward, pages are moved from stk2
back to stk1
. The current page is always at the top of stk1
.
Intuition
Think about how a real browser's back and forward buttons work. When you visit pages A → B → C, then press back twice to return to A, you create a situation where B and C are "ahead" of you - accessible via the forward button. But if you visit a new page D while at A, those forward pages (B and C) disappear forever.
This behavior naturally suggests using two separate data structures: one for pages "behind" us (where back takes us) and one for pages "ahead" of us (where forward takes us).
Why stacks? Consider the order of access:
- When going back, we access the most recently visited page first (LIFO - Last In, First Out)
- When going forward, we revisit pages in the reverse order of how we backed away from them (also LIFO)
The key insight is that the current page should always be part of the "back history" since we need to return to it if we navigate away. So we maintain:
stk1
: Contains the current page at the top, with all previous pages below itstk2
: Contains pages we can go forward to (pages we've backed away from)
When we go back, we're essentially "undoing" our browsing - we pop from stk1
(removing the current page from back history) and push it to stk2
(making it available for forward navigation). The opposite happens when going forward.
The visiting action is straightforward: add the new URL to our back history (stk1
) and clear the forward history (stk2
) since those pages are no longer reachable - just like in a real browser where visiting a new page eliminates the forward button's history.
Learn more about Stack, Linked List and Data Stream patterns.
Solution Approach
The implementation uses two stacks to manage browser history navigation:
Data Structures:
stk1
: Stores the current page and all pages in the backward historystk2
: Stores pages available for forward navigation
Implementation Details:
-
Initialization (
__init__
):- Create two empty stacks:
stk1
andstk2
- Call
visit(homepage)
to add the homepage tostk1
- This ensures the browser starts with the homepage as the current page
- Create two empty stacks:
-
Visit a new URL (
visit
):- Append the new
url
tostk1
(making it the current page) - Clear
stk2
entirely usingstk2.clear()
- This reflects the browser behavior where visiting a new page eliminates all forward history
- Time complexity:
O(1)
- Append the new
-
Navigate backward (
back
):- Use a while loop with two conditions:
steps > 0
: Still have steps to go backlen(stk1) > 1
: Keep at least one page instk1
(can't go back from the first page)
- In each iteration:
- Pop the top element from
stk1
and push it tostk2
- Decrement
steps
by 1
- Pop the top element from
- Return
stk1[-1]
(the new current page at the top ofstk1
) - Time complexity:
O(steps)
- Use a while loop with two conditions:
-
Navigate forward (
forward
):- Use a while loop with two conditions:
steps > 0
: Still have steps to go forwardstk2
is not empty: There are pages to go forward to
- In each iteration:
- Pop the top element from
stk2
and push it tostk1
- Decrement
steps
by 1
- Pop the top element from
- Return
stk1[-1]
(the new current page at the top ofstk1
) - Time complexity:
O(steps)
- Use a while loop with two conditions:
The elegance of this solution lies in how the two stacks naturally maintain the relationship between back and forward histories. The current page is always stk1[-1]
, and the movement between stacks during back/forward operations preserves the correct ordering of pages.
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a sequence of browser operations to see how the two-stack solution works:
Initial State: Start with homepage "google.com"
- After
BrowserHistory("google.com")
:stk1 = ["google.com"]
(current page at top)stk2 = []
(no forward history)
Step 1: Visit "leetcode.com"
- After
visit("leetcode.com")
:stk1 = ["google.com", "leetcode.com"]
stk2 = []
(cleared on visit)
Step 2: Visit "facebook.com"
- After
visit("facebook.com")
:stk1 = ["google.com", "leetcode.com", "facebook.com"]
stk2 = []
Step 3: Visit "youtube.com"
- After
visit("youtube.com")
:stk1 = ["google.com", "leetcode.com", "facebook.com", "youtube.com"]
stk2 = []
Step 4: Go back 1 step
- During
back(1)
:- Pop "youtube.com" from
stk1
, push tostk2
stk1 = ["google.com", "leetcode.com", "facebook.com"]
stk2 = ["youtube.com"]
- Returns: "facebook.com" (top of
stk1
)
- Pop "youtube.com" from
Step 5: Go back 1 more step
- During
back(1)
:- Pop "facebook.com" from
stk1
, push tostk2
stk1 = ["google.com", "leetcode.com"]
stk2 = ["youtube.com", "facebook.com"]
- Returns: "leetcode.com"
- Pop "facebook.com" from
Step 6: Go forward 1 step
- During
forward(1)
:- Pop "facebook.com" from
stk2
, push tostk1
stk1 = ["google.com", "leetcode.com", "facebook.com"]
stk2 = ["youtube.com"]
- Returns: "facebook.com"
- Pop "facebook.com" from
Step 7: Visit "linkedin.com" (demonstrates clearing forward history)
- After
visit("linkedin.com")
:stk1 = ["google.com", "leetcode.com", "facebook.com", "linkedin.com"]
stk2 = []
(cleared! "youtube.com" is lost forever)
Step 8: Try to go back 5 steps (more than available)
- During
back(5)
:- Can only go back 3 steps (keeping at least 1 page in
stk1
) - Move "linkedin.com", "facebook.com", "leetcode.com" to
stk2
stk1 = ["google.com"]
stk2 = ["linkedin.com", "facebook.com", "leetcode.com"]
- Returns: "google.com"
- Can only go back 3 steps (keeping at least 1 page in
This example demonstrates all key behaviors:
- The current page is always at the top of
stk1
- Going back moves pages from
stk1
tostk2
in reverse order - Going forward moves pages from
stk2
back tostk1
- Visiting a new URL clears all forward history in
stk2
- We maintain at least one page in
stk1
when going back
Solution Implementation
1class BrowserHistory:
2 def __init__(self, homepage: str):
3 """
4 Initialize browser history with a homepage.
5 Uses two stacks to maintain backward and forward history.
6 """
7 # Stack to store current browsing history (pages we can go back to)
8 self.history_stack = []
9 # Stack to store forward history (pages we can go forward to)
10 self.forward_stack = []
11 # Visit the homepage to initialize the history
12 self.visit(homepage)
13
14 def visit(self, url: str) -> None:
15 """
16 Visit a new URL, clearing any forward history.
17
18 Args:
19 url: The URL to visit
20 """
21 # Add the new URL to history
22 self.history_stack.append(url)
23 # Clear forward history as we're on a new path
24 self.forward_stack.clear()
25
26 def back(self, steps: int) -> str:
27 """
28 Move backward in history by the specified number of steps.
29
30 Args:
31 steps: Number of pages to go back
32
33 Returns:
34 The current URL after moving back
35 """
36 # Move pages from history to forward stack, keeping at least one page in history
37 while steps > 0 and len(self.history_stack) > 1:
38 # Pop from history and push to forward stack
39 self.forward_stack.append(self.history_stack.pop())
40 steps -= 1
41
42 # Return the current page (top of history stack)
43 return self.history_stack[-1]
44
45 def forward(self, steps: int) -> str:
46 """
47 Move forward in history by the specified number of steps.
48
49 Args:
50 steps: Number of pages to go forward
51
52 Returns:
53 The current URL after moving forward
54 """
55 # Move pages from forward stack back to history stack
56 while steps > 0 and self.forward_stack:
57 # Pop from forward stack and push to history stack
58 self.history_stack.append(self.forward_stack.pop())
59 steps -= 1
60
61 # Return the current page (top of history stack)
62 return self.history_stack[-1]
63
64
65# Your BrowserHistory object will be instantiated and called as such:
66# obj = BrowserHistory(homepage)
67# obj.visit(url)
68# param_2 = obj.back(steps)
69# param_3 = obj.forward(steps)
70
1class BrowserHistory {
2 // Stack to store the current browsing history (from homepage to current page)
3 private Deque<String> historyStack = new ArrayDeque<>();
4 // Stack to store forward history (pages that can be navigated forward to)
5 private Deque<String> forwardStack = new ArrayDeque<>();
6
7 /**
8 * Constructor initializes the browser with a homepage
9 * @param homepage The initial homepage URL
10 */
11 public BrowserHistory(String homepage) {
12 visit(homepage);
13 }
14
15 /**
16 * Visits a new URL, clearing any forward history
17 * @param url The URL to visit
18 */
19 public void visit(String url) {
20 // Add the new URL to history
21 historyStack.push(url);
22 // Clear forward history as we're on a new path
23 forwardStack.clear();
24 }
25
26 /**
27 * Moves back in history by the specified number of steps
28 * @param steps Number of steps to go back
29 * @return The current URL after moving back
30 */
31 public String back(int steps) {
32 // Move pages from history to forward stack
33 // Keep at least one page in history (cannot go back from the first page)
34 while (steps > 0 && historyStack.size() > 1) {
35 forwardStack.push(historyStack.pop());
36 steps--;
37 }
38 // Return the current page
39 return historyStack.peek();
40 }
41
42 /**
43 * Moves forward in history by the specified number of steps
44 * @param steps Number of steps to go forward
45 * @return The current URL after moving forward
46 */
47 public String forward(int steps) {
48 // Move pages from forward stack back to history
49 while (steps > 0 && !forwardStack.isEmpty()) {
50 historyStack.push(forwardStack.pop());
51 steps--;
52 }
53 // Return the current page
54 return historyStack.peek();
55 }
56}
57
58/**
59 * Your BrowserHistory object will be instantiated and called as such:
60 * BrowserHistory obj = new BrowserHistory(homepage);
61 * obj.visit(url);
62 * String param_2 = obj.back(steps);
63 * String param_3 = obj.forward(steps);
64 */
65
1class BrowserHistory {
2private:
3 stack<string> backStack; // Stack to store visited pages (current and previous)
4 stack<string> forwardStack; // Stack to store pages available for forward navigation
5
6public:
7 /**
8 * Constructor: Initialize browser with homepage
9 * @param homepage - The initial page to load
10 */
11 BrowserHistory(string homepage) {
12 visit(homepage);
13 }
14
15 /**
16 * Visit a new URL, clearing forward history
17 * @param url - The URL to visit
18 */
19 void visit(string url) {
20 // Push new URL to back stack
21 backStack.push(url);
22
23 // Clear forward history when visiting a new page
24 // (Similar to how browsers work - forward history is lost after visiting new page)
25 forwardStack = stack<string>();
26 }
27
28 /**
29 * Navigate back in history by specified number of steps
30 * @param steps - Number of pages to go back
31 * @return The current page URL after navigation
32 */
33 string back(int steps) {
34 // Move pages from back stack to forward stack
35 // Keep at least one page in back stack (current page)
36 while (steps > 0 && backStack.size() > 1) {
37 forwardStack.push(backStack.top());
38 backStack.pop();
39 steps--;
40 }
41
42 // Return the current page (top of back stack)
43 return backStack.top();
44 }
45
46 /**
47 * Navigate forward in history by specified number of steps
48 * @param steps - Number of pages to go forward
49 * @return The current page URL after navigation
50 */
51 string forward(int steps) {
52 // Move pages from forward stack back to back stack
53 while (steps > 0 && !forwardStack.empty()) {
54 backStack.push(forwardStack.top());
55 forwardStack.pop();
56 steps--;
57 }
58
59 // Return the current page (top of back stack)
60 return backStack.top();
61 }
62};
63
64/**
65 * Your BrowserHistory object will be instantiated and called as such:
66 * BrowserHistory* obj = new BrowserHistory(homepage);
67 * obj->visit(url);
68 * string param_2 = obj->back(steps);
69 * string param_3 = obj->forward(steps);
70 */
71
1// Stack to store visited pages (current and previous)
2let backStack: string[] = [];
3// Stack to store pages available for forward navigation
4let forwardStack: string[] = [];
5
6/**
7 * Initialize browser with homepage
8 * @param homepage - The initial page to load
9 */
10function BrowserHistory(homepage: string): void {
11 // Clear any existing history
12 backStack = [];
13 forwardStack = [];
14 // Visit the homepage
15 visit(homepage);
16}
17
18/**
19 * Visit a new URL, clearing forward history
20 * @param url - The URL to visit
21 */
22function visit(url: string): void {
23 // Push new URL to back stack
24 backStack.push(url);
25
26 // Clear forward history when visiting a new page
27 // (Similar to how browsers work - forward history is lost after visiting new page)
28 forwardStack = [];
29}
30
31/**
32 * Navigate back in history by specified number of steps
33 * @param steps - Number of pages to go back
34 * @return The current page URL after navigation
35 */
36function back(steps: number): string {
37 // Move pages from back stack to forward stack
38 // Keep at least one page in back stack (current page)
39 while (steps > 0 && backStack.length > 1) {
40 const topPage = backStack.pop()!;
41 forwardStack.push(topPage);
42 steps--;
43 }
44
45 // Return the current page (top of back stack)
46 return backStack[backStack.length - 1];
47}
48
49/**
50 * Navigate forward in history by specified number of steps
51 * @param steps - Number of pages to go forward
52 * @return The current page URL after navigation
53 */
54function forward(steps: number): string {
55 // Move pages from forward stack back to back stack
56 while (steps > 0 && forwardStack.length > 0) {
57 const topPage = forwardStack.pop()!;
58 backStack.push(topPage);
59 steps--;
60 }
61
62 // Return the current page (top of back stack)
63 return backStack[backStack.length - 1];
64}
65
Time and Space Complexity
Time Complexity:
__init__(homepage)
:O(1)
- Simply initializes two empty stacks and calls visit oncevisit(url)
:O(1)
- Appends tostk1
and clearsstk2
, both constant time operationsback(steps)
:O(min(steps, len(stk1) - 1))
- In the worst case, movessteps
elements fromstk1
tostk2
, but limited by the available historyforward(steps)
:O(min(steps, len(stk2)))
- In the worst case, movessteps
elements fromstk2
tostk1
, but limited by the forward history
Space Complexity:
O(n)
, where n
is the total number of URLs in the browsing history. The two stacks stk1
and stk2
together store all visited URLs. At any point in time, the total number of elements across both stacks equals the number of unique visits made (after considering that visiting a new URL clears the forward history). The maximum space used is proportional to the total number of URLs visited.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Forgetting to Clear Forward History on Visit
One of the most common mistakes is forgetting to clear the forward stack when visiting a new URL. This violates the expected browser behavior where visiting a new page eliminates all forward history.
Incorrect Implementation:
def visit(self, url: str) -> None:
self.history_stack.append(url)
# Missing: self.forward_stack.clear()
Why it's wrong: If you navigate back a few pages and then visit a new URL, you should not be able to forward to the pages you previously backed out from. The forward history must be cleared.
Correct Implementation:
def visit(self, url: str) -> None:
self.history_stack.append(url)
self.forward_stack.clear() # Essential to clear forward history
2. Going Back Too Far (Empty Stack Issue)
Another critical mistake is allowing the history stack to become empty when going back, which would cause an index error when trying to return the current page.
Incorrect Implementation:
def back(self, steps: int) -> str:
while steps > 0 and self.history_stack: # Wrong condition!
self.forward_stack.append(self.history_stack.pop())
steps -= 1
return self.history_stack[-1] # This will fail if stack is empty!
Why it's wrong: The condition self.history_stack
allows the stack to become completely empty. When we try to access self.history_stack[-1]
, it will raise an IndexError.
Correct Implementation:
def back(self, steps: int) -> str:
while steps > 0 and len(self.history_stack) > 1: # Keep at least 1 page
self.forward_stack.append(self.history_stack.pop())
steps -= 1
return self.history_stack[-1] # Safe - always has at least one element
3. Incorrect Stack Order During Navigation
Some implementations mistakenly push elements in the wrong order or to the wrong stack during back/forward operations.
Incorrect Implementation:
def back(self, steps: int) -> str:
while steps > 0 and len(self.history_stack) > 1:
# Wrong: pushing to beginning instead of end
self.forward_stack.insert(0, self.history_stack.pop())
steps -= 1
return self.history_stack[-1]
Why it's wrong: Using insert(0, ...)
changes the order of elements in the forward stack, breaking the LIFO (Last In, First Out) property required for correct navigation.
Correct Implementation:
def back(self, steps: int) -> str:
while steps > 0 and len(self.history_stack) > 1:
self.forward_stack.append(self.history_stack.pop()) # Maintain LIFO order
steps -= 1
return self.history_stack[-1]
4. Not Initializing Homepage Properly
Some forget to actually visit the homepage in the constructor, leaving the browser in an invalid state.
Incorrect Implementation:
def __init__(self, homepage: str):
self.history_stack = [homepage] # Direct assignment
self.forward_stack = []
Why it's problematic: While this might seem to work, it bypasses the visit
method's logic. If visit
is later modified to include additional functionality (like logging or validation), the homepage won't benefit from these changes.
Correct Implementation:
def __init__(self, homepage: str):
self.history_stack = []
self.forward_stack = []
self.visit(homepage) # Use the visit method for consistency
5. Off-by-One Errors in Step Counting
Be careful with the loop conditions and step counting to avoid navigating one page too many or too few.
Incorrect Implementation:
def back(self, steps: int) -> str:
while steps >= 0 and len(self.history_stack) > 1: # >= is wrong!
self.forward_stack.append(self.history_stack.pop())
steps -= 1
return self.history_stack[-1]
Why it's wrong: Using steps >= 0
will execute one extra iteration when steps reaches 0, moving back one page more than requested.
Correct Implementation:
def back(self, steps: int) -> str:
while steps > 0 and len(self.history_stack) > 1: # Correct: steps > 0
self.forward_stack.append(self.history_stack.pop())
steps -= 1
return self.history_stack[-1]
Which of the tree traversal order can be used to obtain elements in a binary search tree in sorted order?
Recommended Readings
Stack Intro Imagine you have a pile of books on your desk If you want to add a new book you place it on top If you want to read a book you take it from the top And if you simply want to see which book is on the top you
Linked List Cycle Given a linked list with potentially a loop determine whether the linked list from the first node contains a cycle in it For bonus points do this with constant space Parameters nodes The first node of a linked list with potentially a loop Result Whether there is a loop contained
Median of Data Stream Given a stream of numbers find the median number at any given time accurate to 1 decimal place Example add_number 1 add_number 2 add_number 3 get_median 2 0 add_number 4 get_median 2 5 Try it yourself Explanation Intuition Brute force way is to sort the entire list every time we get a new number This would be O N log N for each add_number operation One step up is to notice that the list is sorted before we add any new number to it So every
Want a Structured Path to Master System Design Too? Don’t Miss This!