completefrontend
Open lab My progress
THE JAVASCRIPT FIELD GUIDE

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.

By the end you’ll be able to
  • 01
    Explain wrapper objectsName the short-lived object JavaScript makes so a string, number, boolean, symbol, or bigint can use a method.
  • 02
    Predict autoboxingSay why a method works on a primitive, but your own property on a primitive is not saved.
  • 03
    Avoid wrapper trapsSpot bugs from new String, new Number, and new Boolean, and get the primitive back out of a wrapper.

How can a string have methods?

In the Data types lesson, you met the primitive values. A primitive is a simple value that is not an object. There are seven kinds:

  • strings, numbers, and booleans
  • symbols and bigints
  • null and undefined

In the Strings lesson, you used methods such as toUpperCase(). A method is a function that you call with a dot after a value. Methods normally belong to objects. So here is a puzzle: "abc" is not an object, so how can it have a method?

The answer is called autoboxing. It works in four steps:

  1. You ask a primitive for a method, like "abc".toUpperCase.
  2. JavaScript makes a short-lived object around the primitive. This object is called a wrapper object.
  3. The wrapper finds the method, and the method runs.
  4. JavaScript throws the wrapper away. The primitive is still just a plain value.
A method gives back a new stringPop out in the code editor (opens in a new tab)JavaScript
const word = "tea";
const loudWord = word.toUpperCase();
console.log(word);
console.log(loudWord);

Line 1 saves the string "tea" in word. Line 2 calls toUpperCase() on it. A wrapper finds the method, and the method gives back a new string, "TEA". Line 2 saves that new string in loudWord. Line 3 prints tea, and line 4 prints TEA.

So the method did not change word. A string can never be changed: a string method always gives back a new string. The wrapper only helped find the method. It never went into word.

word.toUpperCase(). The string "tea" in word goes into a short-lived wrapper object that holds the toUpperCase method. The method gives back a new string, "TEA", which is saved in loudWord. The wrapper is thrown away.word.toUpperCase()"tea"wordtoUpperCasewrapperthrown away after"TEA"loudWord
The string "tea" has no methods of its own. For one moment, JavaScript puts it in a wrapper object that can reach toUpperCase. The method gives back a new string, "TEA". Then the wrapper is thrown away, and word is still "tea".
Real-life analogyA primitive is a phone; a wrapper is its case

Think of a primitive as a phone with no buttons. When you want to use a method, a case with buttons snaps onto the phone. You press one button. Then the case comes off again, and you have the same plain phone.

In real life: A phone with no buttons
In JavaScript: The primitive string "tea"
In real life: A case snapped on for a moment
In JavaScript: A short-lived String wrapper object
In real life: A button on the case
In JavaScript: A method such as toUpperCase
In real life: Taking the case off again
In JavaScript: JavaScript throws the wrapper away
The main idea

Autoboxing means JavaScript puts a primitive inside a short-lived wrapper object, so it can find a method or property. For "abc".toUpperCase(), the wrapper is a String object, and it finds toUpperCase. Then the wrapper is thrown away.

Wrapper objects

COMPARE

Five kinds of primitive have a wrapper. Each kind has a function with the same name and a capital letter:

  • String for strings
  • Number for numbers
  • Boolean for booleans
  • Symbol for symbols
  • BigInt for bigints

Each of these functions has a prototype. A prototype is an object that holds the methods shared by all values of one kind. For example, String.prototype holds toUpperCase, trim, and the other string methods. A later lesson shows how JavaScript searches a prototype. For now, remember one thing: the methods live there.

You can also make a wrapper object yourself with new, like new String("x"). That wrapper does not get thrown away. It is not the same thing as the primitive. The code below compares the two.

A primitive and a wrapper objectPop out in the code editor (opens in a new tab)JavaScript
const primitive = "x";
const wrapped = new String("x");
console.log(typeof primitive, typeof wrapped);
console.log(wrapped.valueOf() === primitive);

Line 1 saves the primitive string "x". Line 2 uses new String("x") to make a wrapper object that holds "x". Line 3 uses typeof, which gives the name of a value's type. It prints string object: the first value is a string, and the second is an object.

Line 4 calls valueOf(). This method gives back the primitive inside a wrapper. The result is "x", which equals primitive, so line 4 prints true.

valueOf() does not change the wrapper. It gives you a separate primitive value. wrapped is still an object.

Compare a primitive with its wrapper
The two valuesPop out in the code editor (opens in a new tab)JavaScript
const primitive = "x";const wrapper = new String("x");
Real results"x" vs new String("x")
typeof primitiveprimitive: string
wrapper: object
=== primitiveprimitive: true
wrapper: false
== primitiveprimitive: true
wrapper: true
truthinessprimitive: true
wrapper: true
JSON.stringifyprimitive: "x"
wrapper: "x"
Object.keysprimitive: ["0"]
wrapper: ["0"]
valueOf()primitive: "x"
wrapper: "x"
Try it yourself

Pick a pair. Each row runs one check on the primitive and on the wrapper. typeof, ===, and truthiness see the wrapper as an object, even though valueOf() gives the same primitive back.

Every result is worked out by running real JavaScript on these two values.

The table compares a primitive with a wrapper object. Read each row as a question, then compare the two answers.

How a primitive and a wrapper object differ
QuestionPrimitiveWrapper object
What is it?A plain value, such as "x", 5, or false.An object made by new String, new Number, or new Boolean.
What does typeof say?"string", "number", or "boolean".Always "object".
Does an if treat it as true?It depends on the value. false is treated as false.Yes, always. An if treats every object as true.
When should you use it?Almost always.Almost never in app code.

Autoboxing: the wrapper is short-lived

STEP THROUGH

A method call works on a primitive because a wrapper finds the method. But what if you try to save your own property on a primitive, like word.extra = 1? That can't work. A primitive has no place to keep a property, and the wrapper is thrown away right after the line runs.

What happens next depends on the mode the code runs in:

  • In sloppy mode, JavaScript quietly ignores the write. Sloppy mode is the older set of rules. It is used when a script has no "use strict" line at the top.
  • In strict mode, JavaScript throws a TypeError. A TypeError is an error that means a value was used in the wrong way.

The code examples on this site run in strict mode, so strict mode is what you'll usually see here.

A method works, but a saved property is lost
Step 0 of 5Ready
Your turn: follow the blue line

Step through the method call first. Then see why a property saved on a primitive is lost. Switch modes to compare sloppy mode with strict mode.

Running in
  1. script
Next: line 1
const upper = word.toUpperCase();word.extra = 1;console.log(word.extra);
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
Run the code in sloppy mode or in strict mode.

Switching modes starts the replay again from the first step.

A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

Step through the replay. Line 1 saves the primitive "hi". Line 2 calls toUpperCase() and gets back a new string, "HI". Line 3 tries to save extra on word. In sloppy mode, the write is ignored, so line 4 prints undefined. In strict mode, the write throws a TypeError, so the code never reaches the read.

The replay follows the rules in the JavaScript specification, the document that says how JavaScript must work. Real JavaScript engines, the programs that run your code, usually skip making the wrapper to save time. Your code still gives the same results as if the wrapper was made.

A real object can keep a propertyPop out in the code editor (opens in a new tab)JavaScript
const word = "tea";
const savedWord = new String("tea");
savedWord.note = "hot";
console.log(savedWord.note);
console.log(word.note);

Line 1 saves the primitive string "tea" in word. Line 2 makes a real wrapper object with new String("tea"). Line 3 saves a note property on that object, so line 4 prints hot. Line 5 reads note from the primitive word. Nothing was ever saved there, so it prints undefined.

Left: word holds the primitive string "tea". Reading word.note gives undefined. Right: savedWord is a String object that holds note: "hot". Reading savedWord.note gives "hot".word.note"tea"wordundefinedsavedWord.notenote: "hot"savedWord"hot"
On the right, savedWord is a real object, so it can keep note. On the left, word is a primitive string. It has no place to keep a property, so word.note is undefined.

Where methods live

STEP THROUGH

A method is not copied onto each string. All strings share one toUpperCase function, kept in String.prototype. In the same way, all numbers share methods such as toFixed, kept in Number.prototype.

Two strings share one methodPop out in the code editor (opens in a new tab)JavaScript
const first = "cat".toUpperCase;
const second = "dog".toUpperCase;
console.log(first === second);
console.log("cat".toUpperCase());

Line 1 reads toUpperCase from "cat" but does not call it, because there are no call brackets (). It saves the function in first. Line 2 does the same for "dog" and saves the function in second. Line 3 checks whether both are the same function, and prints true.

Line 4 calls the method on "cat" and prints CAT. The function is shared, but each call works on its own string.

Two strings, "cat" and "dog". Arrows from both point to the same toUpperCase card, kept in one shared object, String.prototype. So first === second is true.first"cat""dog"secondtoUpperCaseprototypeString.prototype
"cat" and "dog" don't each carry a copy of toUpperCase. Both reach the same one, kept in String.prototype. That is why first === second is true.

Numbers need one extra trick. A number literal is a number written straight into the code, like 5. You can't write 5.toFixed(2), because JavaScript reads the dot as a decimal point, as in 5.5. That gives a SyntaxError, the error for code JavaScript can't read. Two ways work instead:

  • (5).toFixed(2): the brackets end the number first.
  • 5..toFixed(2): the first dot is the decimal point, and the second dot starts the method.

toFixed(2) gives back the number as a string with 2 digits after the decimal point, so both give "5.00".

Symbols and bigints have methods tooPop out in the code editor (opens in a new tab)JavaScript
const orderId = Symbol("order");
const seats = 3n;
console.log(typeof orderId.description, orderId.description);
console.log(seats.toString());

Line 1 makes a symbol, a unique value often used as a hidden property name. Its description is "order". Line 2 makes a bigint, a whole number of any size, written with an n at the end.

Line 3 reads description, which is a string, so it prints string order. Line 4 calls toString(), which turns the bigint into the string "3", so it prints 3. Both kinds get a wrapper for method calls, even though you can't make them with new.

Where methods live
Step 0 of 5Ready
Your turn: follow the blue line

Methods live on prototypes. Step through two ways to call a number method on the number 5.

Running in
  1. script
Next: line 1
const fixedA = (5).toFixed(2);const fixedB = 5..toFixed(2);console.log(sameMethod);console.log(fixedA, fixedB);
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

In the replay, line 1 checks that the method reached from "abc" is the same function as String.prototype.toUpperCase. Lines 2 and 3 use the two ways to call toFixed on 5.

null and undefined have no wrapper

null and undefined are the only two primitives with no wrapper. So reading any property on them, like null.toString or undefined.valueOf, throws a TypeError.

The two values with no wrapperPop out in the code editor (opens in a new tab)JavaScript
console.log(typeof null);
try {
  null.toString();
} catch (error) {
  console.log(error.name);
}
try {
  undefined.valueOf();
} catch (error) {
  console.log(error.name);
}

Line 1 prints object. That is an old mistake in JavaScript that can no longer be fixed. null is not an object.

Lines 2 to 6 try null.toString(). try runs some code, and if that code throws an error, catch runs instead. Here catch prints the error's name, TypeError. Lines 7 to 11 do the same for undefined.valueOf() and print TypeError again.

Why new String() is a trap

SORT

In the Constructors & new lesson, you saw a first warning about wrappers. Here is the full rule:

  • String(x), Number(x), or Boolean(x) without new converts x and gives back a primitive.
  • With new, the same function makes a wrapper object that stays around. That object acts differently in comparisons and in if checks.
Real-life analogynew String() keeps the phone case on

A normal method call snaps the case on for one moment. new String() leaves the case on for good. Anyone who looks at the phone sees the case first.

In real life: Snapping on a case for one call
In JavaScript: Autoboxing for one method call
In real life: Leaving the case on for good
In JavaScript: new String("x") keeps a wrapper object
In real life: Seeing the case first
In JavaScript: typeof, ===, and if see an object
A converted string and a wrapper objectPop out in the code editor (opens in a new tab)JavaScript
const good = String(123);      // "123" primitiveconst trap = new String("x"); // object wrapperconsole.log(typeof good);console.log(typeof trap);console.log(trap === "x");

Line 1 calls String(123) without new and gets the primitive string "123". Line 2 calls new String("x") and gets a wrapper object. Line 3 prints string, and line 4 prints object.

Line 5 prints false. === is strict equality: it checks the type as well as the value. An object is never strictly equal to a string.

Left: String(123) gives the primitive string "123", and typeof says string. Right: new String("x") gives a wrapper object holding "x", and typeof says object.String(123)"123"primitivetypeof: stringnew String("x")"x"wrappertypeof: object
Without new, String(123) gives back a plain string. With new, new String("x") gives back an object that holds the string. Checks such as typeof see the object, not the string inside it.
Each converter without new gives a primitivePop out in the code editor (opens in a new tab)JavaScript
const text = String(123);
const wrappedText = new String(123);
console.log(typeof text, typeof wrappedText);
console.log(typeof Symbol(), typeof BigInt(1));

Line 1 gets a primitive string from String(123). Line 2 gets a wrapper object from new String(123). So line 3 prints string object. Line 4 calls Symbol() and BigInt(1) without new. Both give back primitives, so it prints symbol bigint.

Object() also makes a wrapperPop out in the code editor (opens in a new tab)JavaScript
const boxed = Object("tea");
console.log(typeof boxed);
console.log(boxed.valueOf());

Line 1 calls Object() with a primitive string. Object() makes a wrapper for it, just like new String would. So line 2 prints object. Line 3 calls valueOf(), gets the primitive string back, and prints tea.

The table lists five wrapper surprises. Read each row as: "When I write this, I get this result, and this is why it matters."

Wrapper surprises to avoid
When you writeWhat you getWhy it matters
String(123)The primitive string "123".This is the right way to turn a value into a string.
new String("x")A wrapper object.typeof says "object", and === "x" is false.
new Boolean(false)A wrapper object, which is truthy.An if treats it as true, even though the value inside is false.
new Symbol()A TypeError.Symbols get wrappers for method calls, but you can't make one with new.
new BigInt(1)A TypeError.You can't make a bigint with new either. Write BigInt(1) instead.

Sort each expression by what it gives you: a primitive or a wrapper object.

Primitive or object?
  • String("a")
  • new String("a")
  • Object("a")
  • "a".valueOf()
  • Number("5")
  • new Number(5)
Try it yourself
0 of 6 correct

Use the rule: a converter without new gives a primitive. new and Object(...) make a wrapper object.

Choose a category for every card. You can change an answer at any time; Reset clears them all.

Where you’ll use this

In real apps, use primitives. To convert a value, call String, Number, or Boolean without new. Then type checks, comparisons, JSON, and if checks all work the way you expect.

Clean up values that come into your appPop out in the code editor (opens in a new tab)JavaScript
const rawId = " Asha-7 ";
const id = String(rawId).trim().toLowerCase();
const enabled = Boolean("");
console.log(id);
console.log(enabled);

Line 1 is an ID typed into a form, with extra spaces. Line 2 turns it into a string, removes the spaces at both ends with trim(), and makes it lowercase with toLowerCase(). Line 3 turns an empty string into a boolean, false.

Line 4 prints asha-7, and line 5 prints false. Now the rest of the app gets plain primitives that are easy to check.

Unwrap old data firstPop out in the code editor (opens in a new tab)JavaScript
const oldId = new String(" SAVE ");
const id = oldId.valueOf().trim().toLowerCase();
const price = Number("12.50");
console.log(id, typeof id);
console.log(price, typeof price);

Line 1 stands for a wrapper object that came from older code. Line 2 calls valueOf() to get the primitive string out, then trims it and makes it lowercase. Line 3 converts the text "12.50" with Number, without new. Line 4 prints save string, and line 5 prints 12.5 number.

Boolean() checks truthiness, not wordsPop out in the code editor (opens in a new tab)JavaScript
console.log(Boolean("false"));
console.log(Boolean(""));

Boolean() only asks whether a value is truthy, which means an if would treat it as true. Every string with at least one character is truthy, even "false". So line 1 prints true. Only the empty string "" is falsy, so line 2 prints false.

If a form sends the word "false", check the text itself instead, for example text === "true".

Two small helper functionsPop out in the code editor (opens in a new tab)JavaScript
function normalizeId(id) {  return String(id).trim().toLowerCase();} function isEnabled(flag) {  return Boolean(flag);}
  • Line 2: String(id) turns a number ID or a string ID into a string, so trim() always works.
  • Line 6: Boolean(flag) gives back the primitive true or false.
  • If a wrapper comes in from old code, wrapper.valueOf() gets the primitive out.
  • Don't use Boolean(text) to read words such as "false". Check the text itself.

Common misconceptions

“Primitives are secretly objects.”

No. They stay plain values. Autoboxing only makes a short-lived wrapper to find a method.

“If a method works, my own property should stay too.”

No. Finding a method and saving a property are different jobs. The wrapper is thrown away, so nothing is saved.

“Loose equality makes wrappers safe.”

new String("x") == "x" is true, because == unwraps the object. But ===, typeof, and if still see an object.

“new Boolean(false) acts like false.”

No. It is an object, and an if treats every object as true. That makes Boolean wrappers very risky in if checks.

“Symbol and BigInt work with new because they have wrappers.”

No. They get a wrapper for method calls, but new Symbol() and new BigInt(1) throw a TypeError.

Practice: wrappers and primitives

5 EXERCISES
Exercise 1 · Warm-upPredict the type and the truthiness

Predict both printed lines, then check your answer.

Starter codePop out in the code editor (opens in a new tab)JavaScript
console.log(typeof "hi");
console.log(Boolean(new Boolean(false)));

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · PracticeExplain the missing property

    In sloppy mode, const s = "hi"; s.extra = 1; s.extra; does not keep extra. What value does s.extra give back?

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticeCall toFixed the right way

      Write two working calls that show the number 5 with two digits after the decimal point.

      Starter codePop out in the code editor (opens in a new tab)JavaScript
      console.log((5).toFixed(2));
      console.log(5..toFixed(2));

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · ChallengeFix a wrapper bug

        This code runs the wrong branch and prints delete account. What primitive value should replace new Boolean(false)?

        Starter codePop out in the code editor (opens in a new tab)JavaScript
        const enabled = new Boolean(false);
        if (enabled) {
          console.log("delete account");
        } else {
          console.log("stay safe");
        }

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 5 · ChallengeGet the primitive back

          Use valueOf() to get the primitive string out of a wrapper object.

          Starter codePop out in the code editor (opens in a new tab)JavaScript
          const wrapped = new String("save");
          const plain = wrapped.valueOf();
          console.log(typeof plain, plain === "save");

          Answer, then press Check. Spacing and letter case don’t matter.

            Quiz: check your understanding

            8 QUESTIONS
            Lesson quiz · 8 questionsScore: first tries count
            1. Question 1 of 8What is a wrapper object?

              Choose an answer to see the explanation.

            2. Question 2 of 8In sloppy mode, what does this code print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              const s = "hi";
              s.extra = 1;
              console.log(s.extra);

              Choose an answer to see the explanation.

            3. Question 3 of 8In strict mode, what happens when you save a property on a primitive string?

              Choose an answer to see the explanation.

            4. Question 4 of 8What does this new String code print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              console.log(typeof new String("x"), new String("x") === "x");

              Choose an answer to see the explanation.

            5. Question 5 of 8What does this if print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              if (new Boolean(false)) {
                console.log("runs");
              } else {
                console.log("skips");
              }

              Choose an answer to see the explanation.

            6. Question 6 of 8Which way of calling a method on the number 5 is a SyntaxError?

              Choose an answer to see the explanation.

            7. Question 7 of 8Which call converts a value and gives back a primitive, not a wrapper?

              Choose an answer to see the explanation.

            8. Question 8 of 8Which values have no wrapper at all?

              Choose an answer to see the explanation.

            Key takeaways

            • Primitives are not objects. To find a method, JavaScript puts most of them in a short-lived wrapper object.
            • Methods live on prototypes such as String.prototype and Number.prototype, shared by every value of that kind.
            • Your own properties on a primitive are not saved. Sloppy mode ignores the write; strict mode throws a TypeError.
            • Don't use new String(), new Number(), or new Boolean(). To convert, call them without new.
            • null and undefined have no wrapper, so reading a property on them throws.

            One-line definition: a primitive can use methods because JavaScript puts it in a short-lived wrapper object to find the method, then throws the wrapper away.

            Up next: Type coercion.

            CompleteFrontend Clear concepts. Working examples.