skip to content

In JavaScript, what does `new String('a')` produce, and how does it differ from the primitive string `'a'`?

level: juniorimportance: should knowfreq 55%

answer

  1. two call forms, one function
  2. new builds an object, not a value
  3. typeof says 'object'
  4. === fails, == unwraps via valueOf
  5. Symbol and BigInt refuse new

basics

~10 s

new String('a') creates an object wrapper, not a string: typeof reports 'object' and strict equality with 'a' is false. Calling String('a') without new returns the primitive, which is what you almost always want.

solid answer

~40 s

`String`, `Number` and `Boolean` are dual-purpose. Called as plain functions they convert: `String(42)` returns the primitive `'42'`. Called with `new` they construct a wrapper *object* whose internal slot holds the primitive. So `typeof new String('a')` is `'object'`, `new String('a') === 'a'` is false, and even `new String('a') === new String('a')` is false, because those are two distinct objects. Loose `==` compares true against `'a'` only because it unwraps the object first, and arithmetic like `new Number(5) + 1` gives 6 for the same reason — the wrapper's `valueOf` hands back the primitive. In practice, explicitly constructing wrappers is a bug: it produces values that pass through most code unnoticed and then fail identity checks, `typeof` guards, and `Set`/`Map` key comparisons. Use `String(x)`, `Number(x)`, `Boolean(x)` — never `new`.

code

javascript · 8 lines
javascript
const prim = String('a');
const boxed = new String('a');

console.log(typeof prim, typeof boxed);   // 'string' 'object'
console.log(prim === 'a', boxed === 'a'); // true false
console.log(boxed == 'a');                // true — unwrapped by ==
console.log(boxed.toUpperCase());         // 'A' — methods still work
console.log(JSON.stringify({ v: boxed })); // '{"v":"a"}' — unwrapped again

go deeper

for a junior

Remember the one-line rule: call String, Number and Boolean without new to convert, and never with new. Be ready to say that new String('a') is an object, so typeof gives 'object' and === against 'a' is false.

for a middle

Explain the mechanics: the wrapper stores the primitive in an internal slot, its prototype supplies the methods, and valueOf hands the primitive back whenever a conversion is needed — which is exactly why == passes and === fails.

for a senior

Show where this actually costs you in a running system: values that serialize and log normally but break typeof guards, identity comparisons and keyed lookups. Mention that lint rules exist for it and that boxed values should be normalized at the boundary, not tolerated inward.

for a principal

Frame it as an API-design lesson the language itself learned: dual-purpose call semantics created a value that is observationally almost-but-not-quite its primitive, and the committee refused to repeat it for Symbol and BigInt. Argue for conversions that are total and unambiguous at every layer boundary you own.

## Two different things share one name `String`, `Number` and `Boolean` are each a single function object that behaves in two completely different ways depending on how you call it. Called as an ordinary function, it is a **converter**. `String(42)` returns the primitive string `'42'`, `Number('7')` returns the primitive number `7`, `Boolean(0)` returns the primitive `false`. The result is a primitive — a value with no properties of its own, compared by its value. Called with `new`, it is a **constructor** that builds a wrapper object. The object is an ordinary object whose prototype is `String.prototype` (or `Number.prototype` / `Boolean.prototype`) and which carries the primitive in a hidden internal slot. It is compared by reference, like any other object. ```js typeof String('a') // 'string' typeof new String('a') // 'object' String('a') === 'a' // true new String('a') === 'a' // false new String('a') === new String('a') // false — two distinct objects ``` ## Why the difference usually hides A wrapper object behaves like the primitive almost everywhere, which is exactly what makes it dangerous. Its prototype supplies every method, so `new String('abc').toUpperCase()` returns `'ABC'`. Whenever the language needs a primitive from it — arithmetic, string concatenation, comparison with `<`, template interpolation — it calls the wrapper's `valueOf`, which returns the stored primitive: ```js const n = new Number(5); n + 1 // 6 `${new String('a')}` // 'a' new String('a') == 'a' // true — loose equality unwraps first ``` `JSON.stringify` also unwraps: `JSON.stringify({ a: new String('x') })` produces `'{"a":"x"}'`. So a boxed value can travel through an entire serialization round trip and look completely normal. What does *not* hide it: strict equality, `typeof`, `Object.is`, and any container that compares keys by identity. `new String('a') === 'a'` is false. `typeof x === 'string'` guards reject it. Two separately constructed wrappers holding the same text are two different values everywhere identity matters. ## The classic interview demonstration ```js const a = new String('hello'); const b = new String('hello'); a == b // false — comparing two objects compares references a == 'hello' // true — object vs primitive unwraps the object a === 'hello' // false — no conversion happens ``` The first line surprises people who expect the wrapper to "be" its string. Comparing two objects never looks inside them. ## Symbol and BigInt behave differently The two newer primitive types deliberately refuse the `new` form: ```js new Symbol('id') // TypeError: Symbol is not a constructor new BigInt(1) // TypeError: BigInt is not a constructor ``` They still *have* wrapper objects — the language needs one so that `Symbol.prototype.description` and `BigInt.prototype.toString` can be reachable — but you can only obtain one through `Object(sym)`. This is a design correction: the committee saw how much trouble `new String` caused and closed the door for the new types. `Object(x)` is in fact the generic boxing operation for every primitive: ```js typeof Object('a') // 'object' Object('a') instanceof String // true Object(1n) instanceof BigInt // true Object(null) // {} — null and undefined have no wrapper, you get a plain object ``` ## `null` and `undefined` have no wrapper at all That is why `null.length` throws `TypeError: Cannot read properties of null` — there is no `Null` constructor to box into, so property access on them simply fails. Every other primitive has a corresponding wrapper type. ## Is there ever a reason to construct one? Essentially no. There is no capability a wrapper object gives you that the primitive does not, and the wrapper costs you identity semantics, `typeof` guards and an allocation. The rule is simple and absolute in modern code: use `String(x)`, `Number(x)` and `Boolean(x)` without `new` when you want a conversion, and never write `new String`, `new Number` or `new Boolean` at all. Linters flag it (`no-new-wrappers` in ESLint) precisely because it has no legitimate use. The one place boxing genuinely appears in normal programs is *implicit* and temporary — the engine boxing a primitive so that `'abc'.length` can be read — and that wrapper is created and thrown away without you ever holding a reference to it.

  • Can you box a symbol or a BigInt the same way you box a string?
    Not with `new` — `new Symbol('id')` and `new BigInt(1)` both throw a TypeError, because neither function is a constructor. Wrapper objects for them still exist and you get one from `Object(sym)` or `Object(1n)`; the language needs them so prototype methods like `Symbol.prototype.toString` are reachable. Blocking `new` was a deliberate correction after the trouble `new String` and `new Boolean` caused.
  • If a boxed string reaches JSON.stringify, does the output reveal the problem?
    No. `JSON.stringify` explicitly unwraps String, Number and Boolean wrapper objects to their primitives, so `JSON.stringify(new String('x'))` produces `"x"` and `new Number(5)` produces `5`. That is part of why boxed values survive so long undetected: serialization, logging and template interpolation all look completely normal, and only identity-sensitive code — `===`, `typeof`, Set membership — exposes them.
  • Why does `new String('a') == 'a'` return true while `===` returns false?
    Strict equality compares an object and a string as different types and stops — no conversion, so the result is false. Loose equality has an object-vs-primitive step that converts the object to a primitive first, which calls the wrapper's `valueOf` and yields `'a'`, so the comparison becomes `'a' == 'a'`. The wrapper hasn't become a string; the operator just unwrapped it before comparing.

saying these in an interview costs you the question

  • Thinking new String('a') === 'a' is true
  • Saying typeof new Number(5) is 'number'
  • Believing two wrappers with the same text are equal
  • Treating new Boolean/new String as good defensive coding
  • Claiming new Symbol() returns a symbol

context