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 originalinitvalue 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
valinsidecreateCountergives us this storage. -
We also need to remember the original
initvalue for the reset functionality. Sinceinitis 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 bothvalandinit. -
For the increment and decrement operations, we use prefix operators (
++valand--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
valto the originalinitvalue. The expressionval = initboth 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 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 791/** 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} 861#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 */ 1211/** 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 */ 67Solution 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
++valwhich increasesvalby 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
--valto decreasevalby 1 and return the new value. -
reset():
reset() { return (val = init); }Assigns
initback tovaland 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
valvariable (current counter state) - The
initparameter (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)counternow holds an object with three methods
Step 2: Call increment() twice
counter.increment(); // returns 11 counter.increment(); // returns 12
What happens internally:
- First call:
++valchangesvalfrom 10 to 11, then returns 11 - Second call:
++valchangesvalfrom 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:
--valchangesvalfrom 12 to 11, then returns 11- Current state:
val= 11,init= 10
Step 4: Call reset()
counter.reset(); // returns 10
What happens internally:
val = initassigns 10 back tovaland 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
valand returns the new value initremains 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.
Time and Space Complexity
Time Complexity: O(1) for the createCounter function and O(1) for each method call (increment, decrement, reset).
- The
createCounterfunction performs a constant amount of work: initializing a variablevaland 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
createCounterfunction creates a closure that stores:- One integer variable
valto maintain the current counter value - One integer parameter
initthat 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
createCounterwill 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
Which of these pictures shows the visit order of a depth-first search? 
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 If you prefer videos here's a video that explains recursion in a fun and easy way 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 him
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!