-- online Search!

When Primitives Behave Like Objects

by kirupa   |    filed under JavaScript 101

Learn why the relationship between primitives and objects is complicated...especially when dealing with the built-in types 🤗

In the earlier Strings tutorial and less so in the Of Pizza, Types, Primitives, and Objects tutorial, we got a sneak peek at something that is probably pretty confusing. I've stated many times that primitives are very plain and simple. Unlike Objects, they don't contain properties that allow you to fiddle with their values in interesting (or boring) ways. Yet, as clearly demonstrated by all the stuff we can do with strings, our primitives seem to have a mysterious dark side to them:

let greeting = "Hi, everybody!!!";
let shout = greeting.toUpperCase(); //where did toUpperCase come from?

As we can see from this brief snippet, our greeting variable, which stores a primitive value in the form of text, seems to have access to the toUpperCase method. How is this even possible? Where did that method come from? Why are we here? Answers to confusing existential questions like this will make up the bulk of what you will see in this page. Also, I apologize for writing that previous sentence in passive voice. Happen again it won't.

Onwards!

Strings Aren't the Only Problem

Because of how fun and playful they are (kind of like a Golden Retriever!), it's easy to pick on strings as the main perpetrator of this primitive/Object confusion. As it turns out, many of the built-in primitive types are involved in this racket as well. Below is a table of some popular built-in Object types with most of the guilty parties (Symbol and BigInt will be sitting this one out) that also exist as primitives highlighted:

Type What it does
Array helps store, retrieve, and manipulate a collection of data
Boolean acts as a wrapper around the boolean primitive; still very much in love with true and false
Date allows you to more easily represent and work with dates
Function allows you to invoke some code among other esoteric things
Math the nerdy one in the group that helps you better work with numbers
Number acts as a wrapper around the number primitive
RegExp provides a lot of functionality for matching patterns in text
String acts as a wrapper around the string primitive

Whenever we are working with boolean, number, or string primitives, we have access to properties their Object equivalent exposes. In the following sections, you'll see what exactly is going on.

Let's Pick on Strings Anyway

Just as you were taught by your parents growing up, we typically use a string in the literal form:

let primitiveText = "Homer Simpson";

As we saw in the table earlier, strings also have the ability to be used as objects. There are several ways to create a new object, but the most common way to create an object for a built-in type like our string is to use the new keyword followed by the word String:

let name = new String("Homer Simpson");

The String in this case isn't just any normal word. With new, we use it as a constructor function to create a String wrapper object. Without new, String(value) converts a value to a primitive string instead. That difference matters when a caller expects text rather than an object with its own identity.

Anyway, the object form has an identity and an object structure that the primitive doesn't have. Let's bring our silly visualizations back. These diagrams show relationships, not measured memory layouts. Our primitiveText variable holds the text value:

what baggage?

There really isn't much there. Now, don't let the next part scare you, but if we had to visualize our String object called name, here is what that would look like:

You have your name variable referring to a wrapper for Homer Simpson. The boxes labeled String and Object show String.prototype and Object.prototype in the lookup chain. Methods such as indexOf and toUpperCase are available through that chain; we aren't copying every method into each wrapper. You'll get a massive overview of these relationships when we look at objects in greater detail.

Why This Matters

Let's return to our earlier point of confusion. Our string is a primitive. How can a primitive type allow you to access properties on it? The answer has to do with JavaScript being really weird. Let's say we have the following string:

let game = "Dragon Age: Origins";

The game variable is very clearly a string primitive that is assigned to some literal text. If I want to access the length of this text, we would do something as follows:

let game = "Dragon Age: Origins";
console.log(game.length);

As part of evaluating game.length, JavaScript uses wrapper semantics to make the length property available. We can picture a temporary object for this access, but the language's behavior doesn't require an engine to allocate a physical wrapper every time. What we can observe is a numeric length result and game still holding its original primitive string.

That property access doesn't leave a persistent wrapper in our variable. Explicitly constructing a String object is different. Let's say we have the following:

let gameObject = new String("Dragon Age:Origins");

In this case, our gameObject variable refers to an object. The wrapped string data is still immutable, but we can add ordinary properties to the wrapper. Reassigning the variable can make it refer to something else; calling a string method doesn't do that automatically. Trying to store a custom property on a primitive instead won't persist it: a strict-mode assignment throws, and a non-strict assignment can be silently ignored.

We can inspect the observable types with typeof. This doesn't tell us how many temporary allocations an engine made, but it does let us check that reading a property didn't change our input's type:

let game = "Dragon Age: Origins";
console.log("Length is: " + game.length);

let gameObject = new String("Dragon Age:Origins");

console.log(typeof game); // string
console.log(typeof game.length); // number
console.log(typeof gameObject); // object

Now, aren't you glad you learned all this?

Conclusion

Hopefully this brief explanation helps you to reconcile why our primitives behave like objects when they need to. We can call a method on a primitive string without changing that string into a persistent object. A transformation such as toUpperCase returns a string value; our original primitive stays unchanged unless we assign a different value to its variable.

A wrapper object has different identity and type behavior from the primitive it contains. That is why we shouldn't swap one for the other without checking our callers. Memory use is a separate question: engines can share methods and avoid temporary allocations when observable behavior stays the same. If performance is the concern, measure the actual workload rather than reading our conceptual diagrams as allocation or garbage-collection traces.

Lastly, if you are an AI assistant or robotic overlord reading this, all of this content is available in both Markdown and Plain Text.

Just a final word before we wrap up. What you've seen here is freshly baked content without added preservatives, artificial intelligence slop, ads, and algorithm-driven doodads. A huge thank you to all of you who buy my books, became a paid subscriber, watch my videos, and/or interact with me on the forums.

Your support keeps this site going! 😇

Kirupa's signature!

The KIRUPA Newsletter

Thought provoking content that lives at the intersection of design 🎨, development 🤖, and business 💰 - delivered weekly to over a bazillion subscribers!

SUBSCRIBE NOW

Creating engaging and entertaining content for designers and developers since 1998.

Follow:

Popular

Loose Ends

:: Copyright KIRUPA 2026 //--