cf.completefrontendCode editorOpen lab
THE JAVASCRIPT FIELD GUIDE

Polyfill call, apply & bind

Build myCall, myApply, and myBind while comparing strict this, array-like apply arguments, partial binding, and new-aware constructors.

By the end, you can
  • 01
    Rebuild call and applyUse the temporary Symbol property trick, validate apply's argument bag, and clean up after every call.
  • 02
    Implement bind honestlyReturn a wrapper that locks this, prepends partial arguments, and knows when it was called with new.
  • 03
    Judge polyfill faithfulnessCompare native behavior to the lesson helpers for strict receivers, primitives, metadata, constructors, arrows, and classes.

Rebuild the this controls

The everyday call, apply & bind lesson taught what these methods do. The this keyword and Losing this taught why the call site matters. Now you will rebuild the machinery so the rules stop feeling magical.

Plain definition

A polyfill for call, apply, or bind is a normal JavaScript function that recreates the observable behavior of a built-in function method for older or learning environments. A production polyfill patches carefully; this lesson keeps the helpers standalone.

Real-life analogyA borrowed phone for one call

Imagine borrowing Ada's phone to make one call. call and apply use the chosen phone now; apply takes its extra values from a list. bind saves the chosen phone for later.

In real life: Use Ada's phone for one call
In JavaScript: myCall(fn, receiver, ...args)
In real life: Use a list of contacts for that call
In JavaScript: myApply(fn, receiver, argsLike)
In real life: Save Ada on speed dial
In JavaScript: myBind(fn, receiver, ...preset)

Where the analogy stops: A real phone does not explain strict mode or constructors. A polyfill must document those JavaScript rules.

We will connect the implementation to Methods & this, Functions are objects, Currying & partial application, Constructors & new, F.prototype & constructors, Strict mode, and Symbols. The neighboring build-it-yourself lessons are Polyfill array methods and Build a promise from scratch.

myCall with a temporary Symbol

STEP THROUGH

The core trick is short: put the function on the target object under a unique Symbol, call it as receiver[key](...args), delete the property, then return the original result. The property call is what makes JavaScript set this to the receiver.

Trace the Symbol-based myCall
Step 0 of 7Ready
Your turn: follow the blue line

Step through the classic call polyfill trick: attach with a Symbol, invoke as a method, delete, and return the result.

Running in
  1. script
Next: line 1
Click the blue line to take the next stepPop out in the code editor (opens in a new tab)JavaScript
  if (typeof fn !== "function") throw new TypeError("Target must be callable");  const receiver = thisArg == null ? globalThis : Object(thisArg);  const key = Symbol("myCall");  try {    Object.defineProperty(receiver, key, { value: fn, configurable: true });    return receiver[key](...args);  } finally {    delete receiver[key];  }} function label(prefix, punctuation) {  return prefix + this.name + punctuation;}const user = { name: "Ada" };console.log(myCall(label, user, "Hi, ", "!"));
CallStoreChangeResultRun = next line. Ran = already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

Read the steps line by line. Line 3 chooses the receiver. Line 4 creates a collision-proof key. Line 6 installs the function for one moment. Line 7 invokes it as a method. Line 9 cleans up even if the original function throws, because the cleanup sits in finally.

Why Symbol matters

A string key like "__fn" might overwrite a real property. A fresh Symbol("myCall") is unique, so it avoids property collisions while still being a valid object key.

thisArg: null, undefined, primitives, and strict mode

REAL OUTPUT

Native call passes a receiver into the function call. What the function sees then depends on whether the target function is strict. In a sloppy classic script, null and undefined become globalThis, and primitives are boxed. In a strict function, undefined stays undefined, null stays null, and primitives stay primitive.

Strict vs sloppy receiversPop out in the code editor (opens in a new tab)JavaScript
function sloppyNull() {  return this === globalThis;}function strictUndefined() {  "use strict";  return this === undefined;}function strictNull() {  "use strict";  return this === null;}function sloppyPrimitive() {  return Object.prototype.toString.call(this);}function strictPrimitive() {  "use strict";  return typeof this;} console.log(sloppyNull.call(null));console.log(strictUndefined.call(undefined));console.log(strictNull.call(null));console.log(sloppyPrimitive.call(5));console.log(strictPrimitive.call(5));

A Symbol-property polyfill can match the common sloppy behavior by using Object(thisArg) and globalThis. It cannot perfectly pass undefined into a strict target, because the trick itself must call receiver[key](). That honesty is part of writing a production-quality lesson: know where the trick stops.

myApply: call plus an argument bag

VALIDATION

apply is call with one packaging difference. Its second argument is either null or undefined for “no arguments,” or an object with length and indexed values. A primitive such as 7 is not an argument bag and should throw a TypeError before the original function runs.

myApply delegates to myCallPop out in the code editor (opens in a new tab)JavaScript
function myCall(fn, thisArg, ...args) {  const receiver = thisArg == null ? globalThis : Object(thisArg);  const key = Symbol("myCall");  try {    Object.defineProperty(receiver, key, { value: fn, configurable: true });    return receiver[key](...args);  } finally {    delete receiver[key];  }}function myApply(fn, thisArg, argsArray) {  if (argsArray == null) return myCall(fn, thisArg);  if (typeof argsArray !== "object" && typeof argsArray !== "function") {    throw new TypeError("CreateListFromArrayLike called on non-object");  }  return myCall(fn, thisArg, ...Array.from(argsArray));}function list(a, b) {  return this.prefix + a + " & " + b;}const args = { 0: "Ada", 1: "Grace", length: 2 };console.log(myApply(list, { prefix: "Team: " }, args));

Line 12 accepts null and undefined. Lines 13–15 reject non-objects. Line 16 turns an array-like value into real arguments with Array.from. Today, if you only need to unpack an array, spread syntax is clearer:

Modern spread often replaces applyPop out in the code editor (opens in a new tab)JavaScript
const scores = [4, 9, 2];console.log(Math.max.apply(null, scores));console.log(Math.max(...scores));

myBind: fixed this and preset leading arguments

PARTIAL APPLICATION

bind returns a new function. Normal calls to that wrapper use the original function with the bound this value and a combined argument list: preset values first, later values after them. That is partial application, the same idea explored in the currying lesson.

Native bind facts to copy carefullyPop out in the code editor (opens in a new tab)JavaScript
function multiply(a, b, c) {  return a * b * c;}const nativeDouble = multiply.bind(null, 2);console.log(nativeDouble.name);console.log(nativeDouble.length);console.log(nativeDouble(3, 4)); function showName(punctuation) {  return this.name + punctuation;}const first = showName.bind({ name: "Ada" });const rebound = first.bind({ name: "Grace" });console.log(rebound("!"));

Native bound functions also expose metadata. The name is prefixed with "bound ", and length is the number of remaining parameters after preset arguments. This lesson’s standalone myBind attempts those with Object.defineProperty, but the behavior is best effort because older engines may not let you redefine function metadata.

Binding twice does not replace the first bound this. The second wrapper can add more preset arguments, but the first receiver wins.

bind with new

STEP THROUGH

The constructor case is the part simplified helpers usually miss. When a bound function is called with new, the bound thisArg is ignored. JavaScript creates a fresh instance, prepends the bound arguments, and keeps the original prototype relationship so point instanceof Point is true.

Real-life analogyA form with one blank filled in

Binding a constructor is like filling in one blank before a new form is made. The new form is still the one being filled in. The old placeholder object is not changed.

In real life: The form still belongs to Point
In JavaScript: The original Point.prototype remains in the chain
In real life: The first blank already says 1
In JavaScript: The preset argument 1 becomes x
In real life: A new form is filled out
In JavaScript: new supplies the real this

Where the analogy stops: A paper form cannot return another form. JavaScript constructors can return an object, so a complete helper handles that case.

Trace new BoundPoint(2)
Step 0 of 9Ready
Your turn: follow the blue line

Step through the constructor branch of myBind: new.target changes which this value is forwarded.

Running in
  1. script
Next: line 1
Click the blue line to take the next stepPop out in the code editor (opens in a new tab)JavaScript
  function boundFunction(...later) {    const usingNew = new.target !== undefined;    const receiver = usingNew ? this : thisArg;    const result = fn.apply(receiver, [...preset, ...later]);    return result;  }  if (fn.prototype) {    boundFunction.prototype = Object.create(fn.prototype);  }  return boundFunction;} function Point(x, y) {  this.x = x;  this.y = y;}Point.prototype.label = function () {  return this.x + "," + this.y;}; const BoundPoint = myBind(Point, { x: 99, y: 99 }, 1);const point = new BoundPoint(2);console.log(point.label());console.log(point instanceof Point);
CallStoreChangeResultRun = next line. Ran = already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

Modern helpers can use new.target to detect the constructor branch. Older MDN-style polyfills used an empty bridge function and an instanceof check to make the prototype chain work:

Classic prototype-bridge bind polyfillPop out in the code editor (opens in a new tab)JavaScript
function classicBind(fn, thisArg, ...preset) {  function Empty() {}  function bound(...later) {    const receiver = this instanceof Empty ? this : thisArg;    return fn.apply(receiver, preset.concat(later));  }  if (fn.prototype) {    Empty.prototype = fn.prototype;    bound.prototype = new Empty();    // Keep Empty.prototype intact because bound uses instanceof Empty.  }  return bound;} function Point(x, y) {  this.x = x;  this.y = y;}const BoundPoint = classicBind(Point, { x: 99 }, 1);const point = new BoundPoint(2);console.log(point instanceof Point);

Native vs polyfill: know what matches

COMPARE

A polyfill is only useful when you can name its faithfulness. The table is the map; the lab below produces the values with real JavaScript. The most important mismatch is strict receiver substitution in myCall.

Conformance checklist for this lesson's helpers
CaseStatusWhy
Object receiver with callMatchesTemporary method call supplies the same receiver.
null or undefined in sloppy codeMatches common polyfillBoth substitute globalThis; classic scripts do this.
null or undefined in strict codeIntentional mismatchNative strict functions receive the exact value; the Symbol trick must call through an object.
Primitive receiverMatches sloppy functionsNative sloppy calls box primitives; the helper uses Object(thisArg). Strict primitive receivers are not perfectly emulated.
apply with null argsMatchesNo normal arguments are forwarded.
apply with a number as argsMatches error kindBoth throw TypeError; the helper checks before Array.from.
Partial bindMatchesPreset values are prepended to later arguments.
Binding twiceMatchesThe first bound this wins; later binds can only add arguments.
new with a bound constructorMatches in this helpernew.target ignores bound this and the prototype bridge keeps instanceof Original true.
name and lengthBest effortNative engines set them; this helper attempts Object.defineProperty.
Class constructor with callMatches failureClasses throw because they require new.
Native vs polyfill conformance lab
Conformance case listPop out in the code editor (opens in a new tab)JavaScript
// All helpers are standalone; no built-in prototype is patched.const cases = [  "normal object receiver",  "strict undefined receiver",  "primitive receiver",  "array-like apply arguments",  "partial bind",  "bind with new",  "name and length",  "class constructor call",];for (const label of cases) {  console.log(label);}
call: object receivermatches
Native
Hello, Ada
Polyfill
Hello, Ada
Verdict
same observable result
Try it yourself

The Symbol method trick matches native call for ordinary object receivers.

The values are produced by native JavaScript and by the standalone helpers exported from this lesson. Mismatches are called out instead of hidden.
Class constructors and arrows

Arrow functions do not have their own this, so call, apply, and bind cannot retarget them. Class constructors are the opposite edge: they require new, so calling them through call throws a TypeError.

Arrow and class edge casesPop out in the code editor (opens in a new tab)JavaScript
const arrow = (function makeArrow() {  return () => this.name;}).call({ name: "Ada" });console.log(arrow.call({ name: "Grace" })); class Widget {}try {  Widget.call({});} catch (error) {  console.log(error.name);  console.log(error.message);}

Where you’ll use this

SORT IT

These helpers show up in practical code even when you never ship the polyfill. Event handlers use bind or wrappers to preserve a receiver. Libraries borrow array methods for array-like values. Utility code partially applies repeated options such as logging prefixes, tax rates, locales, or sort directions.

Which helper are you rebuilding?
  • Borrow Array.prototype.join for a string-like object right now
  • Run a function with arguments already stored in { 0: "Ada", length: 1 }
  • Create a callback that remembers view before an event fires later
  • Save the first argument of priceWithTax(rate, price) as 0.08
  • Read older code that does Math.max.apply(null, numbers)
  • Support new BoundPoint(2) after presetting x to 1
Try it yourself
0 of 6 correct

Sort each task by the helper whose behavior it needs.

Choose a category for every card. You can change an answer at any time; Reset clears them all.

Modern JavaScript often prefers clearer syntax once you understand the old pattern: Math.max(...numbers) instead of apply, wrappers instead of repeated bind in hot render paths, and Object.hasOwn instead of a borrowed hasOwnProperty when your target environments support it.

Common misconceptions

  • “The Symbol trick is the spec.” It teaches the call-site mechanism, but it is not a full engine-level implementation.
  • “Strict and sloppy receivers behave the same.” Strict functions keep exact receivers; sloppy functions substitute and box.
  • “apply accepts any iterable.” Native apply wants an object argument list. This lesson uses Array.from for array-like values after validation.
  • “bind only fixes this.” It also presets leading arguments and can be used with new.
  • “Binding twice changes this again.” The first bound receiver wins.
  • “A bound constructor writes into the bound object.” new creates a fresh instance and ignores the bound thisArg.
  • “Classes are just functions for call.” Class constructors throw when invoked without new.
Similar tools that are easy to confuse
ToolChanges this?Runs now?Constructor behavior
callRuns the function immediatelyFirst argument is this; the rest are listed one by oneCan be polyfilled with a temporary Symbol property
applyRuns the function immediatelyFirst argument is this; second is null, undefined, or an array-like objectCan delegate to myCall(...Array.from(args)) after validation
bindReturns a new functionFirst argument is locked this; later arguments are preset leading valuesNeeds a wrapper, metadata best effort, and a constructor branch
Arrow functionsIgnore these this controlscall, apply, and bind cannot replace lexical thisNo useful this polyfill target

Practice exercises

5 EXERCISES
Exercise 1 · Warm-upWrite the core myCall trick

Fill in the helper so the final line prints Hi, Ada!.

Starter codePop out in the code editor (opens in a new tab)JavaScript
function myCall(fn, thisArg, ...args) {
  // 1. Pick a receiver: globalThis for null/undefined, otherwise Object(thisArg).
  // 2. Store fn under a Symbol, call receiver[key](...args), then delete the key.
}
function greet(punctuation) {
  return "Hi, " + this.name + punctuation;
}
console.log(myCall(greet, { name: "Ada" }, "!"));

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · PracticeFix apply when args are null

    The starter calls Array.from(null). Repair it so no-argument apply calls work.

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    function brokenApply(fn, thisArg, argsArray) {
      return fn.call(thisArg, ...Array.from(argsArray));
    }
    function who() { return this.name; }
    try {
      console.log(brokenApply(who, { name: "Ada" }, null));
    } catch (error) {
      console.log(error.name);
    }

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticeValidate apply's argument bag

      Add the validation branch so myApply(noop, null, 7) prints the error name.

      Starter codePop out in the code editor (opens in a new tab)JavaScript
      function myApply(fn, thisArg, argsArray) {
        if (argsArray == null) return myCall(fn, thisArg);
        // Add the same non-object check native apply performs before Array.from.
      }
      function noop() { return "ok"; }
      try {
        myApply(noop, null, 7);
      } catch (error) {
        console.log(error.name);
      }

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · PracticeBind preset arguments

        Implement the normal-call branch of myBind and prove partial application works.

        Starter codePop out in the code editor (opens in a new tab)JavaScript
        function myBind(fn, thisArg, ...preset) {
          return function boundFunction(...later) {
            return fn.apply(thisArg, preset.concat(later));
          };
        }
        function multiply(a, b) { return a * b; }
        const double = myBind(multiply, null, 2);
        console.log(double(6));

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 5 · ChallengeMake myBind support new

          Finish the constructor branch so new BoundPoint(2) prints 1,2 true.

          Starter codePop out in the code editor (opens in a new tab)JavaScript
          function myBind(fn, thisArg, ...preset) {
            function boundFunction(...later) {
              const receiver = new.target ? this : thisArg;
              return fn.apply(receiver, preset.concat(later));
            }
            // Link boundFunction.prototype to fn.prototype here.
            return boundFunction;
          }
          function Point(x, y) { this.x = x; this.y = y; }
          Point.prototype.label = function () { return this.x + "," + this.y; };
          const BoundPoint = myBind(Point, { x: 99 }, 1);
          const point = new BoundPoint(2);
          try {
            console.log(point.label(), point instanceof Point);
          } catch (error) {
            console.log(error.name);
          }

          Answer, then press Check. Spacing and letter case don’t matter.

            Check your understanding

            8 QUESTIONS
            Polyfill call/apply/bind quiz · 8 questionsScore: first tries count
            1. Question 1 of 8Why does the myCall polyfill store the function under a Symbol key before invoking it?

              Choose an answer to see the explanation.

            2. Question 2 of 8What do these strict and sloppy receiver checks print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              function sloppy() { return this === globalThis; }
              function strict() { "use strict"; return this === undefined; }
              console.log(sloppy.call(null));
              console.log(strict.call(undefined));

              Choose an answer to see the explanation.

            3. Question 3 of 8What does native apply print when the argument bag is a number?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              function add(a, b) { return a + b; }
              try {
                console.log(add.apply(null, 7));
              } catch (error) {
                console.log(error.name);
              }

              Choose an answer to see the explanation.

            4. Question 4 of 8What does the partially bound multiplier print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              function multiply(a, b) { return a * b; }
              const double = multiply.bind(null, 2);
              console.log(double(6));

              Choose an answer to see the explanation.

            5. Question 5 of 8What does binding the same function twice print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              function show() { return this.name; }
              const once = show.bind({ name: "Ada" });
              const twice = once.bind({ name: "Grace" });
              console.log(twice());

              Choose an answer to see the explanation.

            6. Question 6 of 8What does this bound constructor snippet print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              function Point(x, y) { this.x = x; this.y = y; }
              Point.prototype.label = function () { return this.x + "," + this.y; };
              const BoundPoint = Point.bind({ x: 99, y: 99 }, 1);
              const point = new BoundPoint(2);
              console.log(point.label());
              console.log(point instanceof Point);

              Choose an answer to see the explanation.

            7. Question 7 of 8What does a class constructor called with call print here?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              class Widget {}
              try {
                Widget.call({});
              } catch (error) {
                console.log(error.name);
                console.log(error.message.includes("cannot be invoked without 'new'"));
              }

              Choose an answer to see the explanation.

            8. Question 8 of 8What metadata does native bind report here?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              function multiply(a, b, c) { return a * b * c; }
              const bound = multiply.bind(null, 2);
              console.log(bound.name);
              console.log(bound.length);

              Choose an answer to see the explanation.

            Key takeaways

            • myCall uses a unique Symbol property so the original function can be invoked as a method of the receiver.
            • myApply is myCall plus argument-list validation and Array.from for array-like values.
            • Strict receiver semantics are the main place a pure Symbol trick is not fully spec-faithful.
            • myBind returns a wrapper that fixes this, prepends preset arguments, and treats new specially with new.target.
            • Native bind metadata, arrows, classes, and constructor return values are the edges that separate a learning helper from a production polyfill.

            Remember the one-liner.
            Rebuilding these methods is really rebuilding call sites: immediate method calls for call/apply, and a reusable wrapper for bind.

            Up next: build a promise from scratch.

            CompleteFrontend Clear concepts. Working examples.