Back to JavaScript
JavaScript
medium
mid

How can a library extend native objects via the prototype chain without breaking consumer code?

Every object has a hidden [[Prototype]] pointer. Property lookup walks this chain. SDKs that need to extend native types do so safely by **subclassing or adding namespaced static helpers**, not patching `Array.prototype` etc. — that pollutes globals and can collide with merchant code. The safe API gives consumers an SDK object whose own prototype chain is private.

4 min read·~10 min to think through

Prototype chain basics

js
const arr = [1, 2, 3];
arr.__proto__ === Array.prototype;             // true
Array.prototype.__proto__ === Object.prototype; // true
Object.prototype.__proto__ === null;            // top of chain

Property lookup on arr.something:

  1. Own properties of arr.
  2. Array.prototype.
  3. Object.prototype.
  4. null → returns undefined.

Inheritance via prototype

js
class Razorpay {
  open() { ... }
}
class CheckoutRazorpay extends Razorpay {
  customMethod() { ... }
}

const c = new CheckoutRazorpay();
c.open();          // inherited via prototype chain
c.customMethod();  // own subclass method

The wrong way for an SDK to "extend natives"

js
// DON'T
Array.prototype.toRazorpayList = function () { /* ... */ };

Why this is bad:

  • Pollutes globals. Merchant code may rely on for (const k in arr) not yielding library methods.
  • Collisions. Two libs add the same method with different semantics.
  • Future-proofing. A new ECMAScript proposal may define the same name; SDK breaks or shadows it.

The right way for an SDK

  1. Subclass when extending.

``js class RazorpayArray extends Array { toRazorpayList() { /* ... */ } } ``

  1. Static helpers in the SDK namespace.

``js import { Razorpay } from "razorpay"; Razorpay.utils.formatAmount(99.5, "INR"); ``

  1. Symbol-keyed extensions if you must put it on existing objects.

``js const KEY = Symbol("razorpay.id"); obj[KEY] = "rzp_123"; // doesn't collide with string keys ``

  1. Encapsulate state in an SDK instance.

``js const rp = new Razorpay({ key }); rp.checkout.open(opts); // no global mutation ``

How property lookup interacts with extension

If the SDK monkey-patched Array.prototype.sort, every [].sort() in the merchant's codebase goes through SDK code — fragile, opaque debugging, version drift.

Modern best practice

  • Ship a class-based or factory-based SDK.
  • Avoid Object.prototype / Array.prototype mutations entirely.
  • Lock down with Object.freeze(Razorpay) to prevent merchants from patching the SDK.
  • Use TypeScript / declaration files so merchant code knows the SDK surface.

Property lookup performance

The JIT optimizes obj.x lookups via inline caches keyed on the hidden class / shape. A monkey-patched prototype changes the shape of every instance — slow path. Another reason not to patch natives.

Interview framing

"Every object has a prototype pointer; property lookup walks the chain. SDKs that want to add behavior should never patch Array.prototype or Object.prototype — they pollute the global namespace, collide with other libs and future ECMAScript additions, and slow down the engine by invalidating inline caches in merchant code. The safe shape is a class-based SDK (new Razorpay({key}).checkout.open()), with extensions as subclasses, static helpers, or Symbol-keyed properties when adding to existing objects. The chain itself is just a mechanism — what matters is keeping your library's surface isolated from globals."

Follow-up questions

  • What's a hidden class / inline cache?
  • Compare class inheritance with Object.create.
  • Why are Symbol keys safe for extending?

Common mistakes

  • Patching Array/Object.prototype in a library.
  • for-in loops over arrays (sees inherited keys).
  • Confusing __proto__ with .prototype (instance vs constructor).

Performance considerations

  • Patching native prototypes invalidates inline caches across the page — measurable slowdowns.

Edge cases

  • Object.create(null) makes a prototype-less object.
  • Subclassing Array — `length` and indexed access subtleties.
  • Frozen prototypes prevent extension entirely.

Real-world examples

  • MooTools (old library that patched natives) vs jQuery (kept its surface separate). Modern SDKs: Stripe, Razorpay, Auth0 — all class-based.

Senior engineer discussion

Seniors discuss inline caches, shape transitions, and why library hygiene matters at scale. They'd flag a PR that adds to `Array.prototype` in a published package.

Related questions