Custom bind, call and apply

Hard100% Free~30 mins#bind-polyfill#call-apply-bind#this-binding#partial-application#constructor-new#implementations
Key Learning Objectives
✓

Understand how JavaScript binds the execution context ('this') dynamically at invocation time.

✓

Implement educational customCall and customApply by temporarily binding the function as a Symbol property on the target context.

✓

Implement customBind returning a wrapped function that merges preset arguments with invocation arguments (partial application).

✓

Handle constructor invocation with the 'new' operator: recognize that 'new' overrides bound 'this' and binds the new instance.

✓

Preserve prototype chains between bound functions and original constructor prototypes using Object.create(fn.prototype).

The Interview Problem

What is logged to the console when custom implementations of call, apply, and bind execute with argument forwarding and constructor invocation?

1function customCall(fn, context, ...args) {
2 const ctx = context ?? (typeof window !== 'undefined' ? window : globalThis);
3 const key = Symbol('fn');
4 ctx[key] = fn;
5 const result = ctx[key](...args);
6 delete ctx[key];
7 return result;
8}
9
10function customApply(fn, context, args = []) {
11 return customCall(fn, context, ...args);
12}
13
14function customBind(fn, context, ...boundArgs) {
15 function boundFunction(...callArgs) {
16 const isNew = this instanceof boundFunction;
17 const targetCtx = isNew ? this : context;
18 return fn.apply(targetCtx, [...boundArgs, ...callArgs]);
19 }
20 if (fn.prototype) {
21 boundFunction.prototype = Object.create(fn.prototype);
22 }
23 return boundFunction;
24}
25
26function greet(greeting, punctuation) {
27 return greeting + ', ' + this.name + punctuation;
28}
29
30function Person(name, role) {
31 this.name = name;
32 this.role = role;
33}
34
35const user = { name: 'Sarah' };
36
37const r1 = customCall(greet, user, 'Hello', '!');
38const r2 = customApply(greet, user, ['Hi', '?']);
39
40const BoundPerson = customBind(Person, { name: 'IgnoredCtx' }, 'Alex');
41const p1 = new BoundPerson('Staff Eng');
42
43const isInstanceOfPerson = p1 instanceof Person;
44const personName = p1.name;
45const personRole = p1.role;
46
47console.log([r1, r2, isInstanceOfPerson, personName, personRole].join(' | '));
Predict Console Output
Interactive Challenge

Select the option that matches what standard ECMAScript prints to the console:

Hello, Sarah! | Hi, Sarah? | true | Alex | Staff Eng

Hello, Sarah! | Hi, Sarah? | false | IgnoredCtx | Staff Eng

Hello, undefined! | Hi, undefined? | true | Alex | Staff Eng

Hello, Sarah! | Hi, Sarah? | true | undefined | Staff Eng

V8 Engine Execution Trace
Step 1 of 6 (Line 34)

customCall temporarily assigns user[Symbol('fn')] = greet and invokes it, producing 'Hello, Sarah!'. The Symbol key is deleted immediately afterward.

Call Stack (Top = Active)
Global Execution Context
customCall(greet, user)
Lexical Scope / Bindings
user:{ name: 'Sarah' }
key:Symbol('fn')
args:['Hello', '!']
Console Stream
> [empty]

Deep Technical Breakdown

Dynamic Execution Contexts in JavaScript

Every non-arrow JavaScript function receives a dynamic this binding determined at the call site. The language provides three standard methods on Function.prototype to control this explicitly:

MethodExecution TimingArguments FormatReturns
call(ctx, arg1, arg2)ImmediateComma-separated listFunction return value
apply(ctx, [arg1, arg2])ImmediateArray of argumentsFunction return value
bind(ctx, arg1, arg2)DeferredComma-separated listNew bound function

The Symbol Method Technique

To invoke a function with an explicit this without native .call():

  1. Generate a unique property key: const key = Symbol();
  2. Attach the function: context[key] = fn;
  3. Invoke: const res = context[key](...args);
  4. Clean up: delete context[key];
  5. Return result.

The new Operator Override Rule

When a bound function is called as a constructor (new BoundFn()):

javascript
const isNew = this instanceof boundFunction;
const targetCtx = isNew ? this : context;

ECMAScript specification explicitly dictates that when new is used on a bound function, the originally bound context is discarded, and this is bound to the freshly instantiated object inheriting from the target function's prototype.

Common Traps & Mistakes

Using a string key like `context.__fn__ = fn`, which can overwrite existing object properties or trigger prototype pollution.

Forgetting to delete the temporary Symbol property after invocation, leaving leftover references on target objects.

Ignoring the `new` operator in `bind`: failing to detect `this instanceof boundFunction` causes constructor calls to mutate the bound context instead of the new instance.

Failing to link prototypes: if `boundFunction.prototype` does not inherit from `fn.prototype`, `instanceof` checks will return false.

FAANG Follow-Up Probes
Probe #1

What happens when you call `.bind()` multiple times on the same function? Which `this` context wins and why?

Probe #2

Why can arrow functions never have their `this` context rebound using call, apply, or bind?

Probe #3

How does the performance of native `Function.prototype.bind` compare to closure-based polyfills in V8 engines?