skip to content

Proxy and Reflect

The reflective layer of the object model: intercepting fundamental operations with Proxy and performing them explicitly with Reflect. It shows up in interviews more and more because frameworks such as Vue 3 and Immer are built directly on it.

part ofJavaScriptoverview, primer and where to startread it →
on this pageshow

explore

questions

15

In JavaScript, what can wrapping an object in a Proxy do that plain getters and setters cannot, and which real use cases does that unlock?

level: middleimportance: must knowfreq 55%

answer

  1. one wrapper for the whole object
  2. keys that do not exist yet
  3. also delete, in, key listing
  4. reactivity, validation, defaults, dynamic clients
  5. a handler call per operation

basics

~20 s

A Proxy intercepts operations on property names that do not exist yet, plus deletes, in-checks, key enumeration and calls, for any target including arrays and functions. Getters and setters only cover properties you declared in advance.

solid answer

~50 s

The difference is coverage. A getter/setter pair has to be attached to a property name you already know, and it only sees reads and writes of that one name. A Proxy sits in front of the whole object, so one handler answers for keys nobody has defined, and it can also intercept `delete`, the `in` operator, key listing, and calls when the target is a function. That is what makes the practical uses possible: reactive state that notices brand-new keys (Vue 3's `reactive()` and Immer's drafts are built on it), one central place to validate every assignment, defaults for missing keys, negative array indices, access logging, and API clients whose methods are invented from the property name. The price is a handler call on every operation, and the proxy being a different object from the target.

go deeper

for a junior

Be able to say what new Proxy(target, handler) produces: a stand-in object whose reads and writes can run your code first, with anything you do not intercept passed through to the target.

for a middle

Explain the coverage difference concretely — a handler covers keys that do not exist yet plus delete, in and key listing — and name two uses that depend on it, such as reactive state and central validation.

for a senior

Show judgment about where the wrapper belongs: keep the raw target from escaping, expect the per-operation cost, and know that the proxy is a distinct object, so identity checks and objects with internal slots need care.

for a principal

Own the API decision. Argue when a Proxy's hidden control flow is worth the debuggability and performance loss versus explicit accessors or plain functions, and set rules for where proxied values may cross module or worker boundaries.

## What a Proxy actually wraps `new Proxy(target, handler)` returns a new object that stands in front of `target`. Every fundamental operation on the proxy — reading a property, writing one, deleting one, asking `in`, listing keys, calling it — is routed to the matching method on `handler` if one exists, and otherwise performed on the target unchanged. An empty handler therefore produces something that behaves almost exactly like the target. The key word is *operation*. An accessor property (a getter/setter pair) is bound to one name that must exist before anything can be intercepted, and it only covers get and set. A Proxy is bound to the object, not to a name, so it can answer for names that were never declared and for operations that are not property reads at all. ```js const counted = new Proxy({}, { get(target, key, receiver) { if (!Reflect.has(target, key)) return 0; // default for unknown keys return Reflect.get(target, key, receiver); } }); counted.anythingAtAll; // 0 — no property was ever defined ``` ## The use cases that genuinely need it **Reactive state.** A store must know when *any* key is read (to record a dependency) and when *any* key is written or deleted (to invalidate). Installing accessors at construction time can only cover the keys present then; adding a key later, or deleting one, is invisible. A Proxy covers the whole object for its whole life, which is why Vue 3's `reactive()` and Immer's copy-on-write drafts are Proxy-based. **Validation and invariants in one place.** A `set` trap runs for every assignment, so range checks, type checks and required-field rules live in one handler instead of being duplicated per property. In strict-mode code — which includes all ES modules — a `set` trap that returns `false` makes the assignment throw a `TypeError`, though throwing your own error with a useful message is friendlier. **Defaults and safe lookup.** A `get` trap that returns a fallback turns missing-key `undefined` into a sensible value; the inverse — throwing on an unknown key — turns a typo in a config object from a silent `undefined` into an immediate error. **Negative array indices and other index tricks.** `arr[-1]` is a plain property read of the key `"-1"`; a `get` trap can translate it into `length - 1`. **Dynamic API clients.** `api.getUser` need not exist. A `get` trap can manufacture a function from the property name, so a client library covers an entire endpoint surface without generating code for it. **Access logging and tracing.** Wrapping a service object in a logging proxy records every call site during debugging without editing the object. ## What it costs Every intercepted operation becomes a call into your handler, so an engine cannot use the inline caches it would use for a plain property read. That is fine for configuration objects and UI state touched at human speed, and a real problem in a loop touching millions of elements. There is also a transparency cost: the proxy is not the target. `proxy !== target`, a lookup in a `Map` or `WeakMap` keyed by the target will not find the proxy, methods that need internal slots (`Map.prototype.get`, `Date.prototype.getTime`, DOM node methods) throw when invoked with the proxy as `this`, and methods reading `#private` fields throw for the same reason. And anyone still holding the raw target writes straight past your traps — a reactivity or validation layer only works if the proxy is the *only* reference that circulates. ## When not to reach for one If you know the property names up front and only need get/set behaviour, accessors are simpler, faster and easier to debug. If you need to freeze behaviour rather than observe it, `Object.freeze` says so directly. And a Proxy adds hidden control flow: a reader of `user.name = x` has no syntactic hint that arbitrary code runs. Use it where the dynamism is the point, and keep the wrapped surface small and documented.

  • Your validating handler rejects a bad value by returning false from the set trap. What does the assignment actually do?
    In strict-mode code — which includes every ES module and class body — the assignment throws a `TypeError` saying the trap returned falsish. In sloppy mode it fails silently, which is worse than useless for validation. Prefer throwing your own error inside the trap so the message names the property and the rule it broke.
  • Why do proxy-based reactivity layers beat installing accessors on every property at initialization?
    Accessors installed once cover only the keys that existed then. Adding a key, deleting a key, or writing a new array index is invisible, so libraries built that way needed explicit set/delete helpers and could not track array length changes well. A Proxy intercepts the whole object for its whole life, including keys invented later.
  • Someone kept a reference to the raw target and assigns to it directly. What happens to your traps?
    Nothing runs. The proxy and the target are two objects sharing state, not one object with a filter; only operations performed *on the proxy* hit the handler. So a validation or reactivity layer must ensure the proxy is the only reference that escapes — create the target inline, or hide it in a closure or module scope.

saying these in an interview costs you the question

  • Says a Proxy makes the target read-only or immutable
  • Thinks the handler must implement every trap
  • Claims getters can intercept properties that do not exist
  • Assumes writes to the raw target still trigger traps
  • Believes proxying is free at run time
  • Says a Proxy and its target are the same object

context

open as a page

JavaScript's Reflect namespace duplicates work that Object.* methods and operators like `in`, `delete` and `new` already do. What is Reflect for, and how does it actually differ from those older APIs?

level: middleimportance: must knowfreq 58%

basics

~20 s

Reflect is a plain namespace object holding one function per internal object operation, matching the Proxy trap list one for one. Unlike the Object.* helpers and operators it mirrors, its methods report failure by returning false and never coerce primitive arguments.

open as a page

In JavaScript, what does `new Proxy(target, handler)` create, what is a trap, and what happens for an operation the handler defines no trap for?

level: juniorimportance: should knowfreq 40%

basics

~20 s

new Proxy(target, handler) returns a new object that routes fundamental operations — property reads, writes, deletes, key listing, calls — to matching handler methods called traps. Any operation whose trap is missing falls through to the target unchanged.

open as a page

A JavaScript Proxy's handler defines an `ownKeys` trap that returns `['a', 'b']`, yet `Object.keys(proxy)` returns an empty array. Which other trap is involved, and why does the result come back empty?

level: middleimportance: should knowfreq 42%

basics

~20 s

Object.keys also calls the getOwnPropertyDescriptor trap for every key that ownKeys reported, to test enumerability. With that trap absent it forwards to the target, which has no such properties, so each key returns undefined and is filtered out.

open as a page

You want arr[-1] to return the last element by wrapping a JavaScript array in a Proxy. What must the get handler do with the property key it receives, and what must it still forward?

level: middleimportance: should knowfreq 35%

basics

~20 s

Property keys reach a get trap as strings or symbols, never numbers, so the handler must convert the key to a number, verify it is a negative integer, and translate it to length plus that index. Everything else must be forwarded unchanged.

open as a page

Reflect.defineProperty and Reflect.set return booleans where Object.defineProperty and a plain assignment throw. Why does Reflect report failure that way, and what does it mean for calling code?

level: middleimportance: should knowfreq 38%

basics

~20 s

Reflect's mutating methods return true or false because Proxy traps are required to return a boolean success flag, so the mirror API had to speak the same protocol. The cost is that a failure is silent unless the caller checks the returned value.

open as a page

Why can a JavaScript Proxy's `get` trap cause a TypeError even when the trap body itself runs without error, and what shape of property on the target triggers it?

level: seniorimportance: should knowfreq 25%

basics

~20 s

Proxies must not lie about the target's own non-configurable properties. If the target holds a non-writable, non-configurable data property, the get trap must return exactly that value; returning anything else makes the engine throw a TypeError after the trap returns.

open as a page

In JavaScript, a Proxy's `get` trap is called with `(target, property, receiver)`. What is `receiver`, and give a case where it is not the proxy itself.

level: seniorimportance: should knowfreq 28%

basics

~20 s

receiver is the object the property access actually started on, and the value that becomes this inside any getter. It is the proxy for a direct access, but the inheriting object when the proxy sits on that object's prototype chain.

open as a page

What does using a JavaScript Proxy cost at run time compared with a plain object, and how do you decide whether that cost matters for a given piece of code?

level: seniorimportance: should knowfreq 30%

basics

~20 s

Every intercepted operation on a proxy becomes a call into a handler function, so engines cannot use the inline caches that make plain property reads nearly free. Expect a large relative slowdown per access and judge it by how many accesses the code performs.

open as a page

A colleague wraps existing objects in a JavaScript Proxy expecting a drop-in replacement. Which kinds of objects stop working through the proxy, and why?

level: seniorimportance: should knowfreq 35%

basics

~20 s

A Proxy is a different object from its target, so anything keyed to the target's identity breaks: methods reading #private fields, built-ins with internal slots such as Map, Set, Date, typed arrays and DOM nodes, and lookups in collections keyed by the original object.

open as a page

A Proxy get trap can return either `target[key]` or `Reflect.get(target, key, receiver)`. What does that third argument of Reflect.get change, and in which situation do the two forms return different values?

level: seniorimportance: should knowfreq 44%

basics

~20 s

The third argument of Reflect.get is the receiver: the value used as this when the property turns out to be a getter. Omitting it makes getters run against the proxy's target, so a proxy sitting on a prototype chain returns the prototype's data instead of the instance's.

open as a page

What does Reflect.apply(fn, thisArg, argsArray) do in JavaScript, and why would you use it instead of writing fn.apply(thisArg, argsArray)?

level: juniorimportance: nice to knowfreq 28%

basics

~20 s

Reflect.apply calls a function with an explicit this value and an array-like list of arguments, exactly as fn.apply would. It goes straight to the function's internal call operation, so it still works when the function object has its own apply property shadowing the inherited one.

open as a page

What does `Proxy.revocable(target, handler)` return in JavaScript, and what happens to the proxy once `revoke()` has been called?

level: middleimportance: nice to knowfreq 24%

basics

~10 s

Proxy.revocable returns a plain object with two properties, proxy and revoke. Calling revoke() permanently disables the proxy: every operation that would reach a trap throws a TypeError, and the target and handler are released.

open as a page

What does the third argument of Reflect.construct(target, argumentsList, newTarget) control, and what can it build that `new target(...)` cannot?

level: seniorimportance: nice to knowfreq 24%

basics

~20 s

newTarget decides which constructor's .prototype the newly created object gets, and becomes the value of new.target inside the call. It lets one constructor allocate the object while a different one supplies the prototype, which is how an ordinary function can produce a real exotic Array or Error instance.

open as a page

You are building a reactive state layer on top of JavaScript Proxy. How do you decide between wrapping the object graph deeply and wrapping only the top level, and what does the deep option force you to build?

level: principalimportance: nice to knowfreq 22%

basics

~20 s

Deep wrapping observes nested mutations without ceremony but requires lazy per-access wrapping, a target-to-proxy cache for stable identity, special handling for collections and class instances, and an unwrap hatch. Shallow wrapping is cheap and predictable but pushes explicit replacement onto callers.

open as a page