2665. Counter II
Problem Description
This problem asks you to create a function createCounter
that implements a simple counter with closure functionality in TypeScript.
The function createCounter
takes one parameter:
init
: an integer that serves as the initial value of the counter
The function should return an object containing three methods:
increment()
: Increases the counter's current value by 1 and returns the new valuedecrement()
: Decreases the counter's current value by 1 and returns the new valuereset()
: Resets the counter back to the originalinit
value and returns it
The key concept being tested here is closures - the returned object's methods need to maintain access to both the current counter value and the original init
value even after createCounter
has finished executing.
For example, if you create a counter with initial value 5:
- Calling
increment()
would return 6 (and the counter is now at 6) - Calling
reset()
would return 5 (counter goes back to initial value) - Calling
decrement()
would return 4 (counter decreases by 1 from 5)
The solution uses a local variable val
to track the current counter value, while keeping init
accessible for the reset functionality. The prefix increment (++val
) and decrement (--val
) operators are used to modify the value before returning it.
Intuition
The core challenge is creating a counter that "remembers" its state between function calls. In JavaScript/TypeScript, when a function finishes executing, its local variables are typically destroyed. However, we need our counter to persist its value across multiple method calls.
This naturally leads us to think about closures - a fundamental concept where inner functions retain access to variables from their outer function's scope, even after the outer function has returned.
Here's the thought process:
-
We need to store the current counter value somewhere that persists between calls. Creating a local variable
val
insidecreateCounter
gives us this storage. -
We also need to remember the original
init
value for the reset functionality. Sinceinit
is a parameter tocreateCounter
, it remains accessible to all inner functions through closure. -
The three methods (
increment
,decrement
,reset
) need to be returned as part of an object. These methods, defined insidecreateCounter
, will form closures that capture bothval
andinit
. -
For the increment and decrement operations, we use prefix operators (
++val
and--val
) rather than postfix because we want to return the value after modification, not before. The prefix operator modifies first, then returns the new value. -
The reset operation simply reassigns
val
to the originalinit
value. The expressionval = init
both performs the assignment and returns the assigned value, which is exactly what we need.
This pattern of returning an object with methods that close over shared state is a common way to create encapsulated, stateful components in JavaScript/TypeScript without using classes.
Solution Approach
The implementation uses the closure pattern to maintain state across function calls. Let's walk through each part of the solution:
1. Type Definition:
type ReturnObj = {
increment: () => number;
decrement: () => number;
reset: () => number;
};
First, we define a TypeScript type ReturnObj
that specifies the structure of the object we'll return. Each method takes no parameters and returns a number.
2. Main Function Structure:
function createCounter(init: number): ReturnObj {
let val = init;
// ... methods defined here
}
The createCounter
function accepts init
as a parameter. We immediately create a local variable val
and initialize it with init
. This val
will serve as our mutable counter state.
3. Method Implementations:
-
increment():
increment() { return ++val; }
Uses the prefix increment operator
++val
which increasesval
by 1 and returns the new value. The prefix operator is crucial here - it modifies first, then returns. -
decrement():
decrement() { return --val; }
Similarly uses the prefix decrement operator
--val
to decreaseval
by 1 and return the new value. -
reset():
reset() { return (val = init); }
Assigns
init
back toval
and returns the assigned value. The assignment expression itself evaluates to the assigned value, so we can return it directly.
4. Closure Mechanism:
When createCounter
returns the object with these three methods, each method maintains access to:
- The
val
variable (current counter state) - The
init
parameter (original value for reset)
Even after createCounter
finishes executing, these variables remain accessible to the returned methods through JavaScript's closure mechanism. Each call to createCounter
creates a new closure with its own independent val
and init
values, allowing multiple counters to exist simultaneously without interfering with each other.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a concrete example to understand how the closure-based counter works:
Step 1: Create a counter with initial value 10
const counter = createCounter(10);
At this point:
init
= 10 (stored in closure)val
= 10 (local variable in closure)counter
now holds an object with three methods
Step 2: Call increment() twice
counter.increment(); // returns 11 counter.increment(); // returns 12
What happens internally:
- First call:
++val
changesval
from 10 to 11, then returns 11 - Second call:
++val
changesval
from 11 to 12, then returns 12 - Current state:
val
= 12,init
= 10 (unchanged)
Step 3: Call decrement() once
counter.decrement(); // returns 11
What happens internally:
--val
changesval
from 12 to 11, then returns 11- Current state:
val
= 11,init
= 10
Step 4: Call reset()
counter.reset(); // returns 10
What happens internally:
val = init
assigns 10 back toval
and returns 10- Current state:
val
= 10,init
= 10
Step 5: Continue using the counter
counter.decrement(); // returns 9 counter.decrement(); // returns 8
The counter continues working from the reset value:
- Each call modifies
val
and returns the new value init
remains at 10 for future resets
Key Insight: Throughout all these operations, the val
and init
variables persist in memory because the returned methods have closed over them. Each method call can read and modify val
, while init
remains constant as our reset reference point.
Solution Implementation
1class ReturnObj:
2 """
3 Type definition for the counter object that will be returned.
4 Contains three methods for manipulating the counter value.
5 """
6 def __init__(self, increment, decrement, reset):
7 self.increment = increment
8 self.decrement = decrement
9 self.reset = reset
10
11
12# Global variable to store the current counter value
13current_value = None
14
15# Global variable to store the initial value for reset functionality
16initial_value = None
17
18
19def increment():
20 """
21 Increments the current counter value by 1.
22
23 Returns:
24 int: The new value after incrementing
25 """
26 global current_value
27 current_value += 1
28 return current_value
29
30
31def decrement():
32 """
33 Decrements the current counter value by 1.
34
35 Returns:
36 int: The new value after decrementing
37 """
38 global current_value
39 current_value -= 1
40 return current_value
41
42
43def reset():
44 """
45 Resets the current counter value to the initial value.
46
47 Returns:
48 int: The reset value (initial value)
49 """
50 global current_value
51 current_value = initial_value
52 return current_value
53
54
55def createCounter(init):
56 """
57 Creates and initializes a counter with the given initial value.
58
59 Args:
60 init (int): The initial value for the counter
61
62 Returns:
63 ReturnObj: An object containing increment, decrement, and reset methods
64 """
65 # Initialize global variables with the provided initial value
66 global initial_value, current_value
67 initial_value = init
68 current_value = init
69
70 # Return an object with references to the global methods
71 return ReturnObj(increment, decrement, reset)
72
73
74# Example usage:
75# counter = createCounter(5)
76# counter.increment() # 6
77# counter.reset() # 5
78# counter.decrement() # 4
79
1/**
2 * Interface definition for the counter object that will be returned
3 * Contains three methods for manipulating the counter value
4 */
5interface ReturnObj {
6 int increment();
7 int decrement();
8 int reset();
9}
10
11/**
12 * Counter class that provides counter functionality with global state
13 */
14public class Counter {
15 // Global variable to store the current counter value
16 private static int currentValue;
17
18 // Global variable to store the initial value for reset functionality
19 private static int initialValue;
20
21 /**
22 * Increments the current counter value by 1
23 * @return The new value after incrementing
24 */
25 private static int increment() {
26 return ++currentValue;
27 }
28
29 /**
30 * Decrements the current counter value by 1
31 * @return The new value after decrementing
32 */
33 private static int decrement() {
34 return --currentValue;
35 }
36
37 /**
38 * Resets the current counter value to the initial value
39 * @return The reset value (initial value)
40 */
41 private static int reset() {
42 currentValue = initialValue;
43 return currentValue;
44 }
45
46 /**
47 * Creates and initializes a counter with the given initial value
48 * @param init The initial value for the counter
49 * @return An object containing increment, decrement, and reset methods
50 */
51 public static ReturnObj createCounter(int init) {
52 // Initialize global variables with the provided initial value
53 initialValue = init;
54 currentValue = init;
55
56 // Return an anonymous inner class implementing ReturnObj interface
57 // with references to the static methods
58 return new ReturnObj() {
59 @Override
60 public int increment() {
61 return Counter.increment();
62 }
63
64 @Override
65 public int decrement() {
66 return Counter.decrement();
67 }
68
69 @Override
70 public int reset() {
71 return Counter.reset();
72 }
73 };
74 }
75
76 /**
77 * Example usage:
78 * public static void main(String[] args) {
79 * ReturnObj counter = createCounter(5);
80 * System.out.println(counter.increment()); // 6
81 * System.out.println(counter.reset()); // 5
82 * System.out.println(counter.decrement()); // 4
83 * }
84 */
85}
86
1#include <functional>
2
3/**
4 * Structure definition for the counter object that will be returned
5 * Contains three methods for manipulating the counter value
6 */
7struct ReturnObj {
8 std::function<int()> increment;
9 std::function<int()> decrement;
10 std::function<int()> reset;
11};
12
13/**
14 * Creates and initializes a counter with the given initial value
15 * @param init - The initial value for the counter
16 * @return A structure containing increment, decrement, and reset methods
17 */
18ReturnObj createCounter(int init) {
19 // Use static variables to maintain state across function calls
20 // Each call to createCounter creates its own set of static variables
21 // Note: This approach has limitations - only one counter instance will work correctly
22 static int current_value;
23 static int initial_value;
24
25 // Initialize the counter values
26 initial_value = init;
27 current_value = init;
28
29 // Create lambda functions that capture the static variables by reference
30 auto increment = []() -> int {
31 return ++current_value;
32 };
33
34 auto decrement = []() -> int {
35 return --current_value;
36 };
37
38 auto reset = []() -> int {
39 current_value = initial_value;
40 return current_value;
41 };
42
43 // Return a structure with the function pointers
44 return ReturnObj{increment, decrement, reset};
45}
46
47/**
48 * Alternative implementation using a class-based approach for better encapsulation
49 * This allows multiple counter instances to work independently
50 */
51class Counter {
52private:
53 int current_value; // Current counter value
54 int initial_value; // Initial value for reset functionality
55
56public:
57 /**
58 * Constructor to initialize the counter
59 * @param init - The initial value for the counter
60 */
61 Counter(int init) : current_value(init), initial_value(init) {}
62
63 /**
64 * Increments the current counter value by 1
65 * @return The new value after incrementing
66 */
67 int increment() {
68 return ++current_value;
69 }
70
71 /**
72 * Decrements the current counter value by 1
73 * @return The new value after decrementing
74 */
75 int decrement() {
76 return --current_value;
77 }
78
79 /**
80 * Resets the current counter value to the initial value
81 * @return The reset value (initial value)
82 */
83 int reset() {
84 current_value = initial_value;
85 return current_value;
86 }
87};
88
89/**
90 * Factory function using the class-based approach
91 * @param init - The initial value for the counter
92 * @return A structure containing increment, decrement, and reset methods
93 */
94ReturnObj createCounterImproved(int init) {
95 // Create a shared pointer to maintain the counter instance
96 auto counter = std::make_shared<Counter>(init);
97
98 // Create lambda functions that capture the counter instance
99 auto increment = [counter]() -> int {
100 return counter->increment();
101 };
102
103 auto decrement = [counter]() -> int {
104 return counter->decrement();
105 };
106
107 auto reset = [counter]() -> int {
108 return counter->reset();
109 };
110
111 return ReturnObj{increment, decrement, reset};
112}
113
114/**
115 * Example usage:
116 * ReturnObj counter = createCounterImproved(5);
117 * counter.increment(); // Returns 6
118 * counter.reset(); // Returns 5
119 * counter.decrement(); // Returns 4
120 */
121
1/**
2 * Type definition for the counter object that will be returned
3 * Contains three methods for manipulating the counter value
4 */
5type ReturnObj = {
6 increment: () => number;
7 decrement: () => number;
8 reset: () => number;
9};
10
11// Global variable to store the current counter value
12let currentValue: number;
13
14// Global variable to store the initial value for reset functionality
15let initialValue: number;
16
17/**
18 * Increments the current counter value by 1
19 * @returns The new value after incrementing
20 */
21function increment(): number {
22 return ++currentValue;
23}
24
25/**
26 * Decrements the current counter value by 1
27 * @returns The new value after decrementing
28 */
29function decrement(): number {
30 return --currentValue;
31}
32
33/**
34 * Resets the current counter value to the initial value
35 * @returns The reset value (initial value)
36 */
37function reset(): number {
38 currentValue = initialValue;
39 return currentValue;
40}
41
42/**
43 * Creates and initializes a counter with the given initial value
44 * @param init - The initial value for the counter
45 * @returns An object containing increment, decrement, and reset methods
46 */
47function createCounter(init: number): ReturnObj {
48 // Initialize global variables with the provided initial value
49 initialValue = init;
50 currentValue = init;
51
52 // Return an object with references to the global methods
53 return {
54 increment,
55 decrement,
56 reset,
57 };
58}
59
60/**
61 * Example usage:
62 * const counter = createCounter(5)
63 * counter.increment(); // 6
64 * counter.reset(); // 5
65 * counter.decrement(); // 4
66 */
67
Time and Space Complexity
Time Complexity: O(1)
for the createCounter
function and O(1)
for each method call (increment
, decrement
, reset
).
- The
createCounter
function performs a constant amount of work: initializing a variableval
and returning an object with three methods. This takesO(1)
time. - Each method (
increment
,decrement
,reset
) performs a single arithmetic operation or assignment, which executes in constant timeO(1)
.
Space Complexity: O(1)
- The
createCounter
function creates a closure that stores:- One integer variable
val
to maintain the current counter value - One integer parameter
init
that remains in the closure scope - One object with three method references
- One integer variable
- The space used is constant regardless of how many times the methods are called or what value is passed as
init
. - Each counter instance created by calling
createCounter
will useO(1)
space independently.
Common Pitfalls
1. Global State Contamination
The Python implementation uses global variables (current_value
and initial_value
), which creates a critical issue: only one counter can exist at a time. Creating a second counter will overwrite the state of the first one.
Problem Example:
counter1 = createCounter(5)
counter2 = createCounter(10)
print(counter1.increment()) # Expected: 6, Actual: 11
print(counter2.increment()) # Expected: 11, Actual: 12
Both counters share the same global state, making them interfere with each other.
Solution: Use closures properly with nested functions to maintain independent state for each counter:
def createCounter(init):
# Local variable specific to this counter instance
current_value = init
def increment():
nonlocal current_value
current_value += 1
return current_value
def decrement():
nonlocal current_value
current_value -= 1
return current_value
def reset():
nonlocal current_value
current_value = init
return current_value
# Return a simple object-like structure
class Counter:
pass
counter = Counter()
counter.increment = increment
counter.decrement = decrement
counter.reset = reset
return counter
2. Using Postfix Instead of Prefix Operators (TypeScript)
A common mistake is using postfix operators (val++
or val--
) instead of prefix operators.
Problem Example:
increment() {
return val++; // Wrong: returns old value, then increments
}
This would return the value before incrementing, not after.
Solution: Always use prefix operators when you need the updated value immediately:
increment() {
return ++val; // Correct: increments first, then returns
}
3. Mutating the Init Parameter
Some might accidentally modify the init
parameter itself instead of maintaining a separate counter variable.
Problem Example:
function createCounter(init: number): ReturnObj {
return {
increment() {
return ++init; // Wrong: modifying parameter directly
},
decrement() {
return --init;
},
reset() {
return init; // This won't work as expected
}
};
}
The reset function becomes useless since init
has been modified.
Solution: Always create a separate variable for the current value:
let val = init; // Separate variable for current state
Consider the classic dynamic programming of longest increasing subsequence:
Find the length of the longest subsequence of a given sequence such that all elements of the subsequence are sorted in increasing order.
For example, the length of LIS for [50, 3, 10, 7, 40, 80]
is 4
and LIS is
[3, 7, 40, 80]
.
What is the recurrence relation?
Recommended Readings
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Runtime Overview When learning about algorithms and data structures you'll frequently encounter the term time complexity This concept is fundamental in computer science and offers insights into how long an algorithm takes to complete given a certain input size What is Time Complexity Time complexity represents the amount of time
Want a Structured Path to Master System Design Too? Don’t Miss This!