Facebook Pixel

2754. Bind Function to Context 🔒

Problem Description

This problem asks you to implement a polyfill for JavaScript's bind method. The goal is to add a method called bindPolyfill to all functions that mimics the behavior of the native bind method.

When bindPolyfill is called on a function with an object as an argument, it should return a new function where the this context is permanently set to the provided object.

Key requirements:

  1. Add bindPolyfill method to the Function.prototype so all functions have access to it
  2. The method takes one parameter: an object that will become the this context
  3. It returns a new function that, when called, executes the original function with the bound this context
  4. The returned function should pass along any arguments it receives to the original function
  5. You cannot use the built-in Function.bind method

Example behavior:

Without binding:

function f() {
  console.log('My context is ' + this.ctx);
}
f(); // Output: "My context is undefined"

With binding using bindPolyfill:

function f() {
  console.log('My context is ' + this.ctx);
}
const boundFunc = f.bindPolyfill({ "ctx": "My Object" });
boundFunc(); // Output: "My context is My Object"

The solution uses Function.prototype to add the method to all functions. Inside bindPolyfill, it returns an arrow function that captures the original function (this) and the object to bind. When the returned function is called, it uses call to invoke the original function with the specified this context and forwards any arguments using the spread operator (...args).

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

Intuition

To understand how to build a bind polyfill, we need to think about what binding actually does: it creates a new function that "remembers" a specific this context, no matter how that function is eventually called.

The key insight is that we need to create a closure - a function that captures and preserves values from its surrounding scope. When bindPolyfill is called, we want to:

  1. Capture the original function - We need to preserve reference to the function we're binding (which is this inside the bindPolyfill method)
  2. Capture the target object - We need to preserve the object that should become the new this context
  3. Return a wrapper function - This new function will call the original with the correct context

Since we can't use the native bind, we need another way to explicitly set the this context when calling a function. JavaScript provides call and apply methods for this exact purpose. The call method allows us to invoke a function with a specific this value.

Why use an arrow function for the returned wrapper? Arrow functions are ideal here because:

  • They automatically capture variables from their enclosing scope (creating the closure we need)
  • They don't have their own this binding, so this inside the arrow function refers to the this from bindPolyfill (which is our original function)

The spread operator (...args) handles the forwarding of arguments elegantly - whatever arguments are passed to the bound function get collected into an array and then spread back out when calling the original function.

This approach essentially manually implements what the native bind does under the hood: creating a closure that preserves both the function and its intended context, then using call to apply that context when the function is invoked.

Solution Approach

The implementation extends the Function.prototype to add the bindPolyfill method to all functions in JavaScript. Here's how the solution works step by step:

1. Extending Function.prototype

Function.prototype.bindPolyfill = function (obj) {
    // implementation here
};

By adding a method to Function.prototype, every function in JavaScript automatically inherits this method. This allows any function to call bindPolyfill.

2. The method signature The method accepts a single parameter obj which is the object that will become the this context for the bound function.

3. Returning a new function

return (...args) => {
    return this.call(obj, ...args);
};

The method returns an arrow function that:

  • Uses the rest parameter syntax ...args to collect all arguments passed to it into an array
  • Inside this arrow function, this refers to the original function (the one bindPolyfill was called on) because arrow functions don't have their own this binding

4. Using call to set context

this.call(obj, ...args)

The call method is used to invoke the original function with:

  • obj as the this context (first parameter to call)
  • ...args spread out as individual arguments (remaining parameters to call)

5. TypeScript declarations

type Fn = (...args) => any;

declare global {
    interface Function {
        bindPolyfill(obj: Record<any, any>): Fn;
    }
}

These TypeScript declarations:

  • Define Fn as a function type that can accept any arguments and return any value
  • Extend the global Function interface to include the bindPolyfill method
  • Specify that bindPolyfill takes a record (object) and returns a function

The beauty of this solution is its simplicity - it leverages JavaScript's closure mechanism and the call method to achieve the same effect as the native bind method in just a few lines of code.

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 see how the bindPolyfill solution works:

// Step 1: Define a function that uses 'this'
function greet(greeting, punctuation) {
  return greeting + ', ' + this.name + punctuation;
}

// Step 2: Create an object to bind as context
const person = { name: 'Alice' };

// Step 3: Call bindPolyfill
const boundGreet = greet.bindPolyfill(person);

When bindPolyfill is called:

  1. Inside bindPolyfill, this refers to the greet function
  2. The obj parameter contains { name: 'Alice' }
  3. An arrow function is created and returned:
    (...args) => {
      return this.call(obj, ...args);  
      // 'this' is captured as the greet function
      // 'obj' is captured as { name: 'Alice' }
    }

Now when we call the bound function:

// Step 4: Call the bound function with arguments
const result = boundGreet('Hello', '!');

Here's what happens internally:

  1. The arrow function receives arguments: ['Hello', '!']
  2. These are collected into args array via rest parameters
  3. The arrow function executes greet.call(person, 'Hello', '!')
  4. Inside greet, this is now person object
  5. The function accesses this.name which is 'Alice'
  6. Returns: 'Hello, Alice!'

Even if we try to change the context later:

const anotherPerson = { name: 'Bob' };
boundGreet.call(anotherPerson, 'Hi', '?');  // Still returns "Hi, Alice?"

The bound context remains person because it was captured in the closure when bindPolyfill was first called. The arrow function always uses the original obj (person) when calling the original function, regardless of how the bound function itself is invoked.

Solution Implementation

1# Type definition: Fn represents a callable that accepts any arguments and returns any type
2# In Python, we use Callable from typing module for this purpose
3from typing import Any, Callable, Dict
4
5# Define type alias for a function that accepts any arguments and returns any type
6Fn = Callable[..., Any]
7
8# Custom implementation of bind functionality as a polyfill
9# Since Python doesn't have prototypes like JavaScript, we'll create a wrapper class
10class FunctionWithBindPolyfill:
11    """Wrapper class to add bindPolyfill method to functions"""
12  
13    def __init__(self, func: Callable):
14        """Initialize with the original function"""
15        self.original_function = func
16  
17    def bindPolyfill(self, obj: Dict[Any, Any]) -> Fn:
18        """
19        Custom polyfill method that mimics the behavior of native bind()
20        Takes an object to be used as 'this' context and returns a new function
21      
22        Args:
23            obj: Dictionary/object to be used as the context for the function
24      
25        Returns:
26            A new function with the bound context
27        """
28        # Store reference to the original function
29        original_function = self.original_function
30      
31        # Return a new function that maintains the bound context
32        def bound_function(*args: Any, **kwargs: Any) -> Any:
33            """
34            Inner function that calls the original with the specified context
35          
36            Args:
37                *args: Variable positional arguments
38                **kwargs: Variable keyword arguments
39          
40            Returns:
41                Result of calling the original function with bound context
42            """
43            # In Python, we simulate 'this' binding by passing obj as first argument
44            # if the original function expects it (like a method would)
45            if hasattr(original_function, '__self__'):
46                # If it's already a bound method, call normally
47                return original_function(*args, **kwargs)
48            else:
49                # Simulate JavaScript's call() by passing obj as self if needed
50                # For regular functions, just call with arguments
51                try:
52                    # Try calling as a method with obj as self
53                    return original_function(obj, *args, **kwargs)
54                except TypeError:
55                    # If that fails, call as a regular function
56                    return original_function(*args, **kwargs)
57      
58        # Return the new bound function
59        return bound_function
60
61
62# Alternative implementation using a decorator approach
63def add_bind_polyfill(func: Callable) -> Any:
64    """
65    Decorator to add bindPolyfill method to a function
66  
67    Args:
68        func: The function to enhance with bindPolyfill
69  
70    Returns:
71        The function with bindPolyfill method added
72    """
73    # Create a wrapper class instance for the function
74    wrapper = FunctionWithBindPolyfill(func)
75  
76    # Copy the original function's attributes
77    wrapper.__name__ = func.__name__
78    wrapper.__doc__ = func.__doc__
79  
80    # Make the wrapper callable like the original function
81    wrapper.__call__ = func
82  
83    # Return the wrapper with bindPolyfill method
84    return wrapper
85
1import java.lang.reflect.Method;
2import java.util.function.Function;
3
4/**
5 * Interface representing a function that accepts variable arguments and returns Object
6 * This is the Java equivalent of TypeScript's Fn type
7 */
8@FunctionalInterface
9interface Fn {
10    Object apply(Object... args);
11}
12
13/**
14 * Custom function wrapper class that provides bind functionality
15 * Since Java doesn't allow extending native classes like Function,
16 * we create a wrapper class to achieve similar behavior
17 */
18class FunctionWrapper {
19    // The original method to be invoked
20    private final Method method;
21    // The object instance that owns the method
22    private final Object instance;
23  
24    /**
25     * Constructor to wrap a method with its instance
26     * @param method The method to be wrapped
27     * @param instance The object instance that owns the method
28     */
29    public FunctionWrapper(Method method, Object instance) {
30        this.method = method;
31        this.instance = instance;
32    }
33  
34    /**
35     * Implementation of bindPolyfill that creates a new function with a fixed 'this' context
36     * @param obj The object to be used as 'this' context when the function is called
37     * @return A new function (Fn) that calls the original method with the bound context
38     */
39    public Fn bindPolyfill(Object obj) {
40        // Store reference to the original method
41        final Method originalFunction = this.method;
42      
43        // Return a new function that maintains the bound context
44        // Using lambda expression which is equivalent to arrow function in TypeScript
45        return (Object... args) -> {
46            try {
47                // Make the method accessible in case it's private
48                originalFunction.setAccessible(true);
49                // Call the original method with the specified 'this' context (obj) and arguments
50                return originalFunction.invoke(obj, args);
51            } catch (Exception e) {
52                // Handle reflection exceptions
53                throw new RuntimeException("Error invoking bound method", e);
54            }
55        };
56    }
57}
58
1#include <functional>
2#include <any>
3#include <vector>
4#include <memory>
5
6// Template class to implement a custom bind polyfill
7// This mimics JavaScript's Function.prototype.bind behavior
8template<typename ReturnType, typename... Args>
9class BindPolyfill {
10private:
11    // Store the original function
12    std::function<ReturnType(Args...)> original_function;
13    // Store the bound object context
14    std::any bound_context;
15  
16public:
17    // Constructor that takes a function and an object to bind as context
18    BindPolyfill(std::function<ReturnType(Args...)> func, std::any obj) 
19        : original_function(func), bound_context(obj) {}
20  
21    // Overloaded function call operator to make this callable
22    // Applies the original function with the bound context
23    ReturnType operator()(Args... args) {
24        // In C++, we simulate the 'this' binding by passing the bound object
25        // as the first parameter if needed, or using it as context
26        return original_function(args...);
27    }
28};
29
30// Helper function to create a bindPolyfill
31// Takes a callable and an object to bind as context
32template<typename Func, typename Context>
33auto bindPolyfill(Func func, Context obj) {
34    // Return a lambda that captures both the function and the context
35    // This lambda forwards all arguments to the original function
36    return [func, obj](auto&&... args) -> decltype(auto) {
37        // If the function is a member function, it would need special handling
38        // For general functions, we simply call with the provided arguments
39        return func(std::forward<decltype(args)>(args)...);
40    };
41}
42
43// Alternative implementation using std::bind-like approach
44template<typename ReturnType, typename ClassType, typename... Args>
45class MemberFunctionBinder {
46private:
47    // Pointer to member function
48    ReturnType (ClassType::*member_func_ptr)(Args...);
49    // Pointer to the object instance
50    ClassType* object_instance;
51  
52public:
53    // Constructor taking member function pointer and object pointer
54    MemberFunctionBinder(ReturnType (ClassType::*func)(Args...), ClassType* obj)
55        : member_func_ptr(func), object_instance(obj) {}
56  
57    // Call operator that invokes the member function on the bound object
58    ReturnType operator()(Args... args) {
59        // Call the member function with the bound object instance
60        return (object_instance->*member_func_ptr)(args...);
61    }
62};
63
64// Utility function to create a member function binder
65// Binds a member function to a specific object instance
66template<typename ReturnType, typename ClassType, typename... Args>
67auto bindPolyfill(ReturnType (ClassType::*func)(Args...), ClassType* obj) {
68    // Return a lambda that captures the member function and object
69    return [func, obj](Args... args) -> ReturnType {
70        // Invoke the member function on the bound object
71        return (obj->*func)(args...);
72    };
73}
74
1// Type definition for a function that accepts any arguments and returns any type
2type Fn = (...args: any[]) => any;
3
4// Extend the global Function interface to include our custom bindPolyfill method
5declare global {
6    interface Function {
7        // Custom polyfill method that mimics the behavior of native bind()
8        // Takes an object to be used as 'this' context and returns a new function
9        bindPolyfill(obj: Record<any, any>): Fn;
10    }
11}
12
13// Implementation of bindPolyfill on Function prototype
14// This method creates a new function with a fixed 'this' context
15Function.prototype.bindPolyfill = function (obj: Record<any, any>): Fn {
16    // Store reference to the original function
17    const originalFunction = this;
18  
19    // Return a new arrow function that maintains the bound context
20    return (...args: any[]): any => {
21        // Call the original function with the specified 'this' context and arguments
22        return originalFunction.call(obj, ...args);
23    };
24};
25

Time and Space Complexity

Time Complexity: O(1)

  • The bindPolyfill method creates and returns a new arrow function, which is a constant-time operation
  • The returned function itself, when invoked, uses call() to execute the original function with the bound context
  • The call() method has O(1) overhead for setting up the execution context
  • The actual time complexity when the bound function executes depends on the original function's implementation, but the binding operation itself is O(1)

Space Complexity: O(1)

  • The bindPolyfill method creates a closure that captures:
    • The reference to the original function (this)
    • The reference to the object (obj)
  • These are fixed-size references regardless of input size
  • The arrow function returned occupies constant space
  • The ...args spread operator in the returned function will create a new array when the function is called, but this is part of the execution space, not the binding space
  • Overall space complexity for the binding operation is O(1)

Common Pitfalls

1. Losing the Original Function Reference

One of the most common mistakes is not properly capturing the original function reference before returning the new bound function. Developers often make this error:

Incorrect Implementation:

Function.prototype.bindPolyfill = function(obj) {
    return function(...args) {
        return this.call(obj, ...args); // ERROR: 'this' is undefined or wrong context
    };
};

Problem: Using a regular function instead of an arrow function causes this inside the returned function to refer to the wrong context (or be undefined in strict mode) rather than the original function.

Solution: Use an arrow function to preserve the lexical this:

Function.prototype.bindPolyfill = function(obj) {
    return (...args) => {
        return this.call(obj, ...args); // Correct: 'this' refers to the original function
    };
};

2. Not Handling Constructor Calls

The native bind method handles the case where the bound function is called with new. The polyfill should account for this:

Incomplete Implementation:

Function.prototype.bindPolyfill = function(obj) {
    return (...args) => {
        return this.call(obj, ...args);
    };
};

Problem: Arrow functions cannot be used as constructors, so new boundFunc() will throw an error.

Better Solution:

Function.prototype.bindPolyfill = function(obj) {
    const originalFunc = this;
    return function(...args) {
        if (new.target) {
            // Called with 'new'
            return new originalFunc(...args);
        }
        return originalFunc.call(obj, ...args);
    };
};

3. Forgetting to Return the Result

A subtle but critical mistake is forgetting to return the result of the function call:

Incorrect:

Function.prototype.bindPolyfill = function(obj) {
    return (...args) => {
        this.call(obj, ...args); // Missing return statement
    };
};

Problem: The bound function will always return undefined regardless of what the original function returns.

Solution: Always return the result:

Function.prototype.bindPolyfill = function(obj) {
    return (...args) => {
        return this.call(obj, ...args); // Properly returns the result
    };
};

4. Not Preserving Partial Application

The native bind method supports partial application (pre-filling arguments), but basic polyfills often miss this:

Limited Implementation:

Function.prototype.bindPolyfill = function(obj) {
    return (...args) => {
        return this.call(obj, ...args);
    };
};

Full Implementation with Partial Application:

Function.prototype.bindPolyfill = function(obj, ...boundArgs) {
    return (...args) => {
        return this.call(obj, ...boundArgs, ...args);
    };
};

5. TypeScript Type Safety Issues

When implementing in TypeScript, not properly typing the return value can lead to loss of type information:

Poor Typing:

Function.prototype.bindPolyfill = function(obj: any): any {
    return (...args: any[]) => {
        return this.call(obj, ...args);
    };
};

Better Typing:

type Fn<T = any> = (...args: any[]) => T;

declare global {
    interface Function {
        bindPolyfill<T>(this: Fn<T>, obj: Record<any, any>): Fn<T>;
    }
}

Function.prototype.bindPolyfill = function<T>(obj: Record<any, any>): Fn<T> {
    return (...args: any[]): T => {
        return this.call(obj, ...args);
    };
};

These pitfalls highlight the importance of understanding JavaScript's execution context, the differences between arrow functions and regular functions, and the full behavior of the native bind method when creating a polyfill.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which algorithm is best for finding the shortest distance between two points in an unweighted graph?


Recommended Readings

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

Load More