Facebook Pixel

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:

  1. increment(): Increases the counter's current value by 1 and returns the new value
  2. decrement(): Decreases the counter's current value by 1 and returns the new value
  3. reset(): Resets the counter back to the original init 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.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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:

  1. We need to store the current counter value somewhere that persists between calls. Creating a local variable val inside createCounter gives us this storage.

  2. We also need to remember the original init value for the reset functionality. Since init is a parameter to createCounter, it remains accessible to all inner functions through closure.

  3. The three methods (increment, decrement, reset) need to be returned as part of an object. These methods, defined inside createCounter, will form closures that capture both val and init.

  4. 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.

  5. The reset operation simply reassigns val to the original init value. The expression val = 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 increases val 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 decrease val by 1 and return the new value.

  • reset():

    reset() {
        return (val = init);
    }

    Assigns init back to val 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 Evaluator

Example 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 changes val from 10 to 11, then returns 11
  • Second call: ++val changes val 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 changes val 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 to val 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 variable val and returning an object with three methods. This takes O(1) time.
  • Each method (increment, decrement, reset) performs a single arithmetic operation or assignment, which executes in constant time O(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
  • 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 use O(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
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

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

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More