I still remember the day I almost charged a customer $1010 for a $20 item.
I was a junior developer building a shopping cart. The price was 10. The shipping was 10. I wrote a simple function to add them together: total = price + shipping. I expected 20. JavaScript gave me "1010".
I stared at the screen, panic rising. I had just discovered the chaos of JavaScript data types. One variable was a number, the other was a string coming from an input field, and JavaScript decided to glue them together instead of doing math.
If you have ever seen [object Object] on your screen, or wondered why changing one object somehow changed another one entirely, you are struggling with data types.
In this article, we aren't just going to list the types like a textbook. We are going to rip them open. We will explore how JavaScript stores them in memory, why they behave so strangely, and how to stop them from breaking your application.
What Are JavaScript Data Types? (The Plain English Version)
Data types are simply the categories of information JavaScript can understand.
Think of them like containers in a kitchen. You have specific jars for liquids (Strings), baskets for fruits (Objects), and maybe a sticky note for a quick number (Number). JavaScript handles each of these containers differently. Crucially, JavaScript is "loosely typed," meaning it doesn't force you to label the jar. It guesses what's inside based on what you put in it. Sometimes, it guesses wrong.
The Mental Model: Post-it Notes vs. Safety Deposit Boxes
To understand the two main categories of data types—Primitives and Reference Types—I use this analogy:
1. Primitives (The Post-it Note)
Imagine a small Post-it note. You can write a number or a word on it. If you want to give that information to a friend, you don't give them your note. You copy the information onto a new Post-it note and give them that. If your friend changes their note, your note stays exactly the same. This is how Numbers, Strings, and Booleans work. They are copied by value.
2. Reference Types (The Safety Deposit Box)
Now imagine a massive safety deposit box full of documents. You can't carry this box around. Instead, you write the key number (the memory address) on a slip of paper. When you share this data with a friend, you don't copy the entire box. You just copy the key number on their slip of paper. Now you both have keys to the same box. If your friend opens the box and rips up a document, the next time you open the box, that document is ripped for you too. This is how Objects, Arrays, and Functions work. They are shared by reference.
Step-by-Step Code Execution
Let’s look at a code example that proves this distinction. This is the source of thousands of bugs in React and Node.js.
1// 1. Primitive Behavior
2let price1 = 50;
3let price2 = price1; // Copy the value
4price2 = 100; // Change the copy
5
6console.log(price1); // 50 (Unchanged!)
7console.log(price2); // 100
8
9// 2. Reference Behavior
10let user1 = { name: "Alice" };
11let user2 = user1; // Copy the KEY, not the object
12user2.name = "Bob"; // Change the object inside the box
13
14console.log(user1.name); // "Bob" (CHANGED!)
15console.log(user2.name); // "Bob"
16What is happening here?
Step 1: The Primitive Copy
- Line 2: The engine creates a memory slot for
price1and puts50directly in it. - Line 3: The engine creates a new slot for
price2. It looks atprice1, sees50, and copies50into the new slot. - Line 4: We change
price2. The engine goes to theprice2slot and wipes50, replacing it with100. Theprice1slot is untouched.
Step 2: The Reference Copy
- Line 10: The engine creates the object
{ name: "Alice" }in a large memory area called the Heap. It gives it an address, say#A1. Then, it goes to the Stack, createsuser1, and writes#A1inside it. - Line 11: The engine creates
user2. It looks atuser1, sees#A1, and copies#A1intouser2. - Crucially: It did not create a new object. Both variables point to the exact same spot in the Heap.
- Line 12: We use
user2to access the Heap at address#A1and change "Alice" to "Bob". - Line 14: We use
user1to look at address#A1. The value there is now "Bob".
How It Works Internally: Stack vs. Heap
To master data types, you must understand where they live.
The JavaScript engine has two memory spaces:
- The Call Stack: This is for simple data. It is fast, structured, and limited in size. Primitives live here. When you declare
let age = 25, the number 25 sits directly on the stack. - The Memory Heap: This is for complex, unstructured data. It is massive and slower. Reference Types (Objects/Arrays) live here.
When you declare an object, the variable on the Stack acts as a pointer (a reference). It holds the memory address (like 0x0045A) that points to the actual data sitting in the Heap.
This architecture explains why comparing objects is so tricky:
1const box1 = { size: 10 };
2const box2 = { size: 10 };
3
4console.log(box1 === box2); // FALSE!
5Why false? Because box1 holds address #A1 and box2 holds address #A2. JavaScript compares the addresses, not the contents.
The 8 Data Types of JavaScript
There are exactly 8 standard data types in the latest ECMAScript specification. Let’s break them down practically.
1. Number
JavaScript is weird here. We don't have Integer or Float. We just have Number.
It represents both integers (10) and decimals (10.5).
The Trap: Floating point math. 0.1 + 0.2 !== 0.3. It equals 0.30000000000000004. Be careful with money calculations!
2. String
Text data. You can use single quotes ', double quotes ", or backticks ```.
Pro Tip: Always use backticks (Template Literals) if you need to insert variables: Hello ${name}.
3. Boolean
true or false. Simple, but vital for logic.
4. Null
This confuses everyone. null means "intentionally empty." It is a value you assign to say, "I have cleared this variable."
The Bug: typeof null returns "object". This is a 20-year-old bug in JavaScript that can't be fixed without breaking the internet. Just ignore it.
5. Undefined
This means "variable declared, but no value assigned yet."
Difference: null is a box you emptied. undefined is a box you haven't opened yet.
6. Symbol (ES6)
Used to create unique identifiers for object properties. You rarely use this in daily app development, mostly for library building.
7. BigInt (ES2020)
For massive numbers that Number cannot handle (integers larger than 2^53 - 1).
8. Object
The parent of everything else: Arrays, Functions, Dates, Regex. They are all objects under the hood.
Common Mistakes Developers Make
1. The null vs undefined Check
I see this check in almost every pull request I review.
1// BAD
2if (user.age !== undefined && user.age !== null) { ... }
3
4// GOOD (Usually)
5if (user.age != null) { ... }
6Why? Using != (loose equality) with null automatically checks for both null and undefined. It is one of the few times loose equality is acceptable.
2. Accidental String Concatenation
The example from my introduction.
1const x = 10 + "5"; // "105"
2const y = "5" * 2; // 10 (Math works?!)
3The Rule: The + operator prefers strings. If one side is a string, it converts the other to a string. The *, -, and / operators prefer numbers. They try to convert strings to numbers.
3. Mutating State in React/Redux
React relies on immutability. If you mutate an object directly, React won't know it changed and won't re-render.
1// BAD - React won't see this change
2const [user, setUser] = useState({ name: "Alice" });
3user.name = "Bob";
4
5// GOOD - Create a new reference
6setUser({ ...user, name: "Bob" });
7Real-World Use Cases
1. API Responses (JSON)
When you fetch data from an API, it comes as a JSON string. You parse it into a JavaScript Object.
1const response = await fetch('/api/user');
2const data = await response.json(); // data is now a Reference Type
3If you pass data to a function that modifies it, you are modifying the original data for your entire app. Always be careful when passing objects around.
2. Form Input Validation
HTML input fields always return strings. Even <input type="number">.
1const age = document.getElementById('age').value; // "25" (String)
2if (age === 25) { ... } // This will FAIL because "25" !== 25
3The Fix: Always explicitly convert inputs: Number(age).
3. Deep Copying Objects
Sometimes you really do need a separate copy of an object (not just a reference).
Until recently, we had to use hacks like JSON.parse(JSON.stringify(obj)).
Now, modern browsers support structuredClone(obj).
1const original = { name: "Alice", details: { age: 30 } };
2const copy = structuredClone(original);
3copy.details.age = 31;
4
5console.log(original.details.age); // 30 (Safe!)
6Interview Perspective
If I am interviewing you, I will ask about data types to test your foundational knowledge.
The Standard Question
"What is the difference between == and ===?"
Answer: == allows type coercion (it tries to convert types to match). === checks both value and type. Always use ===.
The Tricky Example
"What is the output of this code?"
1let a = [1, 2, 3];
2let b = [1, 2, 3];
3let c = a;
4
5console.log(a === b);
6console.log(a === c);
7Answer: false, true.
Explanation: a and b are two distinct arrays in memory (different addresses), even though their content is the same. c is assigned the reference of a, so they point to the exact same address.
TL;DR / Key Takeaways
- Primitives (String, Number, Boolean) are copied by value. They are safe.
- Objects/Arrays are copied by reference. Changing one changes all copies.
- Stack vs Heap: Primitives live on the Stack; Objects live in the Heap.
- Type Coercion: JavaScript tries to be helpful by converting types automatically. It usually causes bugs. Be explicit.
typeof nullis "object". Just accept it and move on.
FAQ
Q: Is array a data type?
A: No. In JavaScript, arrays are technically Objects. typeof [] returns "object". To check if something is an array, use Array.isArray(myVar).
Q: Why does JavaScript have undefined AND null?
A: It is a design quirk. Generally, let JavaScript assign undefined (to errors or empty vars), and you, the developer, should assign null when you want to explicitly reset a value.
Q: How do I check if a variable is a Number?
A: typeof x === 'number' works, but watch out for NaN (Not a Number), which ironically has a type of "number". A safer check is Number.isFinite(x).
Conclusion
Understanding JavaScript data types is the difference between writing code that works "most of the time" and code that is rock-solid.
When you understand that objects are just references, you stop mutating state accidentally. When you understand type coercion, you stop getting "1010" instead of 20.
Next time you see a variable, don't just see the value. Ask yourself: Is this a Post-it note, or is this a key to a safe deposit box? That simple question will save you hours of debugging.
Now, go check your if statements for == and replace them with ===. It is the best first step you can take.
