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?
answer
- one wrapper for the whole object
- keys that do not exist yet
- also delete, in, key listing
- reactivity, validation, defaults, dynamic clients
- a handler call per operation
basics
~20 sA 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 sThe 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
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.
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.
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.
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