Methods on primitives
Learn how "abc".toUpperCase() works when strings are primitives, why autoboxing uses temporary wrapper objects, and why new String(), new Number(), and new Boolean() cause surprising bugs.
- 01Explain wrapper objectsName the temporary object JavaScript uses for string, number, boolean, symbol, and bigint property lookups.
- 02Predict autoboxingTell why methods work but custom properties on primitives do not stick.
- 03Avoid wrapper trapsSpot
new String,new Number, andnew Booleanbugs and convert wrappers back to primitives.
The puzzle
In the Data types lesson, strings, numbers, booleans, symbols, bigints, null, and undefined were the primitive values. In the Strings lesson, you used methods such as toUpperCase(). That creates a puzzle: if "abc" is not an object, how can it have a method?
The short answer is autoboxing. When JavaScript needs a property or method on most primitives, it briefly views the primitive through a wrapper object, uses the method, then throws that wrapper away. The primitive is still just the value.
Imagine a primitive as a phone. A method call briefly adds a case with buttons, uses one, then removes the case.
- In real life: A phone
- In JavaScript: The primitive string
"hi" - In real life: A case fitted for a moment
- In JavaScript: A temporary
Stringwrapper object - In real life: Buttons on the case
- In JavaScript: Methods from
String.prototype - In real life: Taking the case off
- In JavaScript: The wrapper disappears after the lookup
Where the analogy stops: The engine does not have to allocate a visible object every time. The wrapper is the specification model for property lookup; modern engines optimize the common cases.
Autoboxing is JavaScript’s temporary conversion of a primitive into its wrapper object for property access, such as finding String.prototype.toUpperCase for "abc".toUpperCase().
Wrapper objects
COMPAREJavaScript has wrapper constructors named String, Number, Boolean, Symbol, and BigInt. Their prototype objects hold the methods. One sentence for the next stage: methods live on objects such as String.prototype, and the prototype chain explains how lookup reaches them.
A wrapper object is not the same thing as its primitive. Compare the primitive note with the permanent sleeve below.
const primitive = "x";const wrapper = new String("x");primitive: stringwrapper: objectprimitive: truewrapper: falseprimitive: truewrapper: trueprimitive: truewrapper: trueprimitive: "x"wrapper: "x"primitive: ["0"]wrapper: ["0"]primitive: "x"wrapper: "x"Pick a pair and compare real operations. Notice how wrappers are objects for typeof, strict equality, and truthiness, even when valueOf() gives the primitive back.
| Question | Primitive | Wrapper object |
|---|---|---|
| What is it? | A plain value such as "x", 5, or false | An object made by new String, new Number, or new Boolean |
typeof | string, number, or boolean | Always object |
| Truthiness | Depends on the primitive: false is falsy | Every wrapper object is truthy |
| Best use | Almost always what you want | Rarely useful in app code |
Autoboxing: the sleeve is temporary
STEP THROUGHHere is the key behavior. A method call works because JavaScript can look through a temporary wrapper. A property assignment does not stick because the primitive itself has nowhere to store it. Sloppy scripts silently ignore the write; strict code throws a TypeError. Site example modules are strict, so the strict branch is the behavior you will see in normal lesson code.
Step through the method call first, then watch why a property set on a primitive does not stick. Switch modes to compare sloppy scripts with strict code.
script
const upper = word.toUpperCase();word.extra = 1;console.log(word.extra);The replay labels the wrapper conceptually. It is not an engine debugger. It shows the rule that the specification describes, while real engines usually optimize the temporary wrapper away.
Where methods live
STEP THROUGHThe method is not copied onto each string. Lookup reaches shared methods, such as String.prototype.toUpperCase and Number.prototype.toFixed. Number literals need a tiny syntax trick: (5).toFixed(2) or 5..toFixed(2) works, but 5.toFixed(2) is a syntax error because the parser treats the dot as a decimal point.
Methods live on wrapper prototypes. Step through two safe ways to call a number method on a literal.
script
const fixedA = (5).toFixed(2);const fixedB = 5..toFixed(2);console.log(sameMethod);console.log(fixedA, fixedB);null and undefined have no wrappers. Reading a property such as null.toString or undefined.valueOf throws a TypeError instead of autoboxing.
Why new String() is a trap
SORTThe Constructors & new lesson introduced the wrapper trap. Here is the deeper rule: calling String(x), Number(x), or Boolean(x) without new converts and returns a primitive. Adding new buys a permanent sleeve: an object that behaves differently in comparisons and conditions.
Autoboxing adds the case for a moment. new String() keeps it on, so every check sees an object first.
- In real life: Putting on a case for one call
- In JavaScript: Autoboxing for one method lookup
- In real life: Keeping the case on
- In JavaScript:
new String("x")keeping a wrapper object - In real life: Seeing the case first
- In JavaScript:
typeof,===, andifsee an object
Where the analogy stops: A real sleeve can be useful clothing. Primitive wrapper objects are almost never useful in everyday JavaScript; prefer primitive values.
const good = String(123); // "123" primitiveconst trap = new String("x"); // object wrapperconsole.log(typeof good);console.log(typeof trap);console.log(trap === "x");| Expression | Real result | Why it matters |
|---|---|---|
String(123) | Primitive "123" | Good when you want explicit conversion. |
new String("x") | Object wrapper | typeof is object, and strict equality with "x" is false. |
new Boolean(false) | Truthy object | An if runs even though the inner value is false. |
new Symbol() | TypeError | Symbols can be boxed temporarily, but cannot be constructed with new. |
new BigInt(1) | TypeError | BigInts also cannot be constructed with new. Use BigInt(1). |
Sort each expression by whether it leaves you with a primitive or an object.
String("a")new String("a")Object("a")"a".valueOf()Number("5")new Number(5)
Use the rule: converters without new return primitives; new and Object(...) create wrappers.
Where you’ll use this
Most production code should use primitive values and the converter functions without new. That keeps type checks, equality, JSON, and conditions predictable.
function normalizeId(id) { return String(id).trim().toLowerCase();} function isEnabled(flag) { return Boolean(flag);}String(id)is a safe way to accept a number or string ID before trimming it.Boolean(flag)converts to the real primitivetrueorfalse.wrapper.valueOf()recovers the primitive if a wrapper arrives from old code.
Common misconceptions
“Primitives are secretly objects.”
No. They stay primitive values. Autoboxing is a temporary lookup rule.
“If a method works, properties should stick too.”
Method lookup and property storage are different. The wrapper used for lookup is discarded.
“Loose equality means wrappers are safe.”
new String("x") == "x" is true, but ===, typeof, and truthiness still differ.
“new Boolean(false) behaves like false.”
Every object is truthy. This is why Boolean wrappers are especially dangerous in if statements.
“Symbol and BigInt work with new because they have wrappers.”
They can autobox for method lookup, but their constructors throw when used with new.
Practice: wrappers and primitives
5 EXERCISESPredict both lines, then check your answer.
console.log(typeof "hi");
console.log(Boolean(new Boolean(false)));The first line prints string. The second prints true because new Boolean(false) is an object, and objects are truthy.
In a sloppy script, const s = "hi"; s.extra = 1; s.extra; does not keep extra. What value comes back?
It reads back as undefined. The assignment uses a temporary wrapper, then that wrapper is discarded before the later read.
Write two valid calls that format the literal 5 with two decimal places.
console.log((5).toFixed(2));
console.log(5..toFixed(2));console.log((5).toFixed(2));
console.log(5..toFixed(2));Both valid forms call the number method and return the string 5.00. Avoid 5.toFixed(2), which is a syntax error.
This code enters the wrong branch. What primitive value should replace new Boolean(false)?
const enabled = new Boolean(false);
if (enabled) {
console.log("delete account");
} else {
console.log("stay safe");
}const enabled = false;
if (enabled) {
console.log("delete account");
} else {
console.log("stay safe");
}Replacing the wrapper with the primitive false makes the condition skip the dangerous branch.
Use valueOf() to turn a wrapper back into the primitive it contains.
const wrapped = new String("save");
const plain = wrapped.valueOf();
console.log(typeof plain, plain === "save");const wrapped = new String("save");
const plain = wrapped.valueOf();
console.log(typeof plain, plain === "save");valueOf() unwraps the object and returns the primitive string, so typeof plain is string and strict equality succeeds.
Quiz: check your understanding
8 QUESTIONSQuestion 1 of 8What is a wrapper object?
Choose an answer to see the explanation.
Question 2 of 8In a sloppy script, what does the string property example print?
Read the code, then predictconst s = "hi"; s.extra = 1; console.log(s.extra);Choose an answer to see the explanation.
Question 3 of 8What does strict code do when assigning a property to a primitive string?
Choose an answer to see the explanation.
Question 4 of 8What does the
new Stringtype and equality snippet print?Read the code, then predictconsole.log(typeof new String("x"), new String("x") === "x");Choose an answer to see the explanation.
Question 5 of 8What does the
new Boolean(false)condition print?Read the code, then predictif (new Boolean(false)) { console.log("runs"); } else { console.log("skips"); }Choose an answer to see the explanation.
Question 6 of 8Which number-literal method call is a syntax error?
Choose an answer to see the explanation.
Question 7 of 8Which call converts and returns a primitive instead of making a wrapper?
Choose an answer to see the explanation.
Question 8 of 8Which values have no wrapper methods at all?
Choose an answer to see the explanation.
Key takeaways
- Primitives are not objects, but most can be temporarily boxed for property lookup.
- Methods live on wrapper prototypes such as
String.prototypeandNumber.prototype. - Properties assigned to primitives do not stick; strict code throws.
- Avoid
new String(),new Number(), andnew Boolean(); use converters withoutnew. nullandundefinedhave no wrappers.
One-line definition: methods on primitives work because JavaScript temporarily boxes the value for lookup, then returns to the primitive.
Up next: Type coercion.