Skip to content
Sagar Shiroya
Go back

Mastering JS Data Types: Primitives, Objects, and Coercion Pitfalls

JavaScript is the only language, developers don’t learn to use before using it.

Mostly developers say that JavaScript don’t have types as we don’t need to explicitly mentioned type when declaring variables.

That’s why we need to provide extra focus on types. Becaues if we don’t then it would implicitly convert or change the behavior.

For example, 18 and "18" are different for JavaScript Engine.

One is Number, another is String.

There’s no point to debate whether JavaScript has data types or not. Being developer, we should aware about each type and its behaviors.

Table of contents

Open Table of contents

Built-in Data Types

JS has built-in seven types:

  1. null
  2. undefined
  3. boolean
  4. number
  5. string
  6. symbol
  7. object

Everything except object is of primitive types.

typeof

In JavaScript, we can use typeof to inspect the data type of the value.

typeof undefined; // "undefined"
typeof null; // "object" , JS bug (gotcha)
typeof "Sagar"; // "string"
typeof 18; // "number"
typeof "18"; // "string"
typeof { name: "Sagar" }; // "object"
typeof true; // "boolean"
typeof Symbol(); // "symbol"

Buggy Behavior: typeof null returns object.


Value Types

Like other languages (C, C++, Java), JavaScript variables don’t have data types. Javascript has value types.

var x = "Sagar";
x = 30;

Variable can hold string in one assignment, and number next.

undefined vs. undeclared

Variable is undefined, if it has no value. It is not same as undeclared.

undefined has been declared and accessible in the scope, but has no value. undeclared variable is not accessible in the given scope.

var a;

a; // undefined
b; // undeclared, ReferenceError

typeof a; // undefined
typeof b; // undefined

Arrays

Javascript arrays are containers that can hold any value types. While other language enforce you to have all values of same type.

var a = [11, "2", [333]];
a[0]; // 11
a[2][0]; // 333
// Sparse Array
var a = [];
a[0] = 11;
// a[1] => undefined
a[2] = 33;

Note: Be careful when creating sparse arrays. (Empty slots in between)

Arrays have numerical index. You can also add string keys. String keys won’t be counted when counting length of the array.

var a = [];

a[0] = 11;
a[1] = 33;
a["name"] = "Sagar";

a.length; // 2
// [GOTCHA]
var a = [];
a["15"] = 18;

a.length; // 16

Strings

Understanding of String might be array of characters. It may not use Array.

But when writing code, it’s hard to differentiate.

var a = "foo";
var b = ["f", "o", "o"];

a.length; // 3
b.length; // 3

a.indexOf("o"); // 1
b.indexOf("o"); // 1

var c = a.concat("bar"); // "foobar"
var d = b.concat(["b", "a", "r"]); // ["f","o","o","b","a","r"]

a === c; // false
b === d; // false

Both are array of characters, right? Not Actually.

a[1] = "O";
b[1] = "O";

a; // "foo", unchanged, strings are immutable
b; // ["f", "O", "o"], arrays are mutable

Many of the array methods can be useful for string, but not available.

Workaround

  1. Convert stringarray
  2. Perform the desired operation using array method
  3. Convert arraystring again
var a = "ABCD";
var b = a
  .split(" ") // Convert to array
  .reverse() // Perform array operation
  .join(""); // Convert to string

console.log(b); // "DCBA"

Numbers

JavaScript has one Numeric type. It includes:

  1. Integer
  2. Fractional Decimal Numbers

Numbers are expressed as base-10 decimal literals.

var a = 18; // 18.0 - 0 is optional
var b = 18.7;

var a = 0.45; // .45 - leading 0 is optional

var a = 18.0; // 18. - trailing 0 is optional - weird syntax, but valid

.toFixed() specify how many fractional decimal places to represent value with.

var a = 18.745;

a.toFixed(0); // "19" - round off to near decimal
a.toFixed(1); // "18.7"
a.toFixed(2); // "18.75"
a.toFixed(3); // "18.745"
a.toFixed(4); // "18.7450"
a.toFixed(5); // "18.74500"

.toPrecision() specify how many digits should be used to represent the value.

var a = 18.745;

a.toPrecision(1); // 2e+1
a.toPrecision(2); // "19"
a.toPrecision(3); // "18.7"
a.toPrecision(4); // "18.75"
a.toPrecision(5); // "18.745"
a.toPrecision(6); // "18.7450"
a.toPrecision(7); // "18.74500"

Can’t use number directly with this function

18.toFixed(3); // Syntax Error

// Alternate Solutions
(18).toFixed(3); // "18.000"
0.457.toFixed(4); // "0.4570"
18..toFixed(3); // "18.000" (double dots)
18 .toFixed(3); // "18.000" (space before dot)

Decimal Values

0.1 + 0.2 === 0.3; // false

0.1 and 0.2 in binary floating point are not exact. When they are added result is not exactly 0.3. It’s really close 0.30000000000000004

To solve this, we can use Number.EPSILON to compare two numbers for equality.

function numberEquals(n1, n2) {
  return Math.abs(n1 - n2) < Number.EPSILON;
}

var a = 0.1 + 0.2;
var b = 0.3;

numberEquals(a, b); // True

Special Values

null is an empty value.
undefined is a missing value.

null had a value and doesn’t anymore. undefined hasn’t had value yet.

Any operation you perform without both operands as numbers will fail to produce valid number and resulted into NaN.

NaN is “Not a Number”. Same as “Invalid Number” or “Bad Number”.

// [GOTCHA]
typeof NaN; // "number"
NaN === NaN; //false

For comparison, we should use Number.isNaN() instead of === .

var a = 18 / "King";
var b = "king";

Number.isNaN(a); // true
Number.isNaN(b); // false

Infinity

Divided by zero results to Infinity. JS result to Infinity or -Infinity.

var a = 1 / 0; // Infinity
var b = -1 / 0; // -Infinity

a = Number.MAX_VALUE;
a = a + a; // Infinity

Type of Zero (0 and -0)

Javascript has two type of zero:

  1. normal zero (0)
  2. negative zero (-0)
var a = 0 / -7; // -0
var b = 0 * -7; // -0

Addition and subtraction can’t result into negative zero.

// [GOTCHA]
var a = 0; // 0
var b = 0 * -7; // -0

a == b; //true
-0 == 0; // true

a === b; // true
a > b; // false

Only way to compare 0 and -0.

1 / 0; // Infinity
1 / -0; // -Infinity

Value vs. Reference

Primitive(simple) types are passed by value:

Compound values(object) create copy of reference on passing.

// Pass by value
var a = 1;
var b = a;
a = 2;

a; // 2
b; // 1

// Pass by reference
var a = [1, 2, 3];
var b = a; // same reference
b === a; // true

b = [1, 2, 3]; // reference changed
b === a; //false

b = a;
a = [4, 5, 6];
b; // [1,2,3]
a; // [4,5,6]

Coercion

Type Casting

Coercion

var a = 18;

var b = a + ""; // Implicit coercion
var c = String(a); // Explicit coercion

toString()

When any non-string value is coerced into string, conversion handled by toString() method.

Objects will use default Object.prototype.toString(), if you don’t override with your own implementation.

Arrays have default toString() which concatenate all values with comma seperated.

var a = [1, 2, 3];
a.toString(); // "1,2,3"

ToNumber

When any non-number value used in a way where it’s required to be number, such as mathematical operation then it will use ToNumber operation.

ToBoolean

In JavaScript, 1 and true & 0 and false are not same. Other languages, it might be true.

For JavaScript, one is number and another is boolean. We can coerce 0 to false, 1 to true.

Falsy Values Only below values in JS are falsy values, rest all are truthy values.

All values except above values treats as truthy value when coercion will be happened in JavaScript.

Explicit Coercion: Between String and Numbers

This is simple and most common coercion. We can use built-in String() and Number() functions.

var a = 18;
var b = String(a); // "18"

var c = "3.14";
var d = Number(c); // 3.14

Other ways to convert these values are in below example:

var a = 18;
var b = a.toString(); // "18"

var c = "3.14";
var d = +c; // 3.14, unary operator form
var e = -c; // -3.14, - flip the sign and convert

Parsing Numeric Strings

We have built-in parseInt() or parseFloat() methods to parse numeric string. It stops parsing from left to right when non numeric character encounters.

var a = "18";
var b = "18runs";

parseInt(a); // 18
parseInt(b); // 18

Parsing vs Coercion

Parsing a numeric value from string is tolerant of non-numeric characters. Coercion is not tolerant and fails, it resulted into NaN.

var b = "18runs";

Number(b); // NaN , coercion
parseInt(b); // 18 , parsing

Explicit Coercion to Boolean

Boolean() is explicit way to force ToBoolean coercion. Like + opeator coerce to Number. ! operator coerce to Boolean. Problem is that it negates the value as well. So we have to flip the value again by adding one more !

var a = "O";
var b = [];
var c = {};

var d = "";
var e = 0;
var f = null;
var g; // undefined

!!a; // true
!!b; // true
!!c; // true

!!d; // false
!!e; // false
!!f; // false
!!g; // false

Loose Equality Coercion (==)

var a = 42;
var b = "42";
a == b; // true

What kind of coercion happens here? a becomes string or b becomes number? Below rules followed for this kind of coercion:

  1. If Type(x) is Number and Type(y) is String,
    return the result of the comparison x == ToNumber(y).
  2. If Type(x) is String and Type(y) is Number,
    return the result of the comparison ToNumber(x) == y.

convert string to number

Comparison anything to boolean

var a = "42";
var b = true;

a == b; // false

Below rules followed for coercion from Boolean to Number:

  1. If Type(x) is Boolean,
    return the result of the comparison ToNumber(x) == y.
  2. If Type(y) is Boolean,
    return the result of the comparison x == ToNumber(y).

Steps:

  1. converted true to 1
  2. check both are same type and number
  3. convert “42” to 42
  4. compare 42 == 1, results to false

convert boolean to number

Comparing null to undefined

var a = null;
var b; // undefined

a == b; // true
a == null; // true
b == null; // true

Rules:

  1. If x is null and y is undefined, return true.
  2. If x is undefined and y is null, return true.

Comparing object to non-object

If object(object/function/array) compare to simple primitive type, then ES5 specs say:

  1. If Type(x) is either String or Number and Type(y) is Object, return the result of the comparison x == ToPrimitive(y).
  2. If Type(x) is Object and Type(y) is either String or Number, return the result of the comparison ToPrimitive(x) == y.

convert object to primitive type

var a = 42;
var b = [42];

a == b; // true

// Steps:
// 1. [42] converts to "42" as ToPrimitive coercion
// 2. 42 == "42" still different type
// 3. "42" converts to 42 (number)
// 4. 42 == 42 , return true
var a = "abc";
var b = Object(a);

a == b; // true

// Steps:
// 1. b is coerced to underlying "abc" primitive value
// 2. both are same "abc" == "abc", return true

Falsy Comparisons

"0" == null; // false

null is only loosely equal to undefined But null is not equal to anything else even with coercion.

"0" == undefined; // false

No type conversion happens with null and undefined if they compare it with other types than null and undefined.

Coercion Examples with Steps

"0" == false; // true

// Steps:
// 1. false -> 0
// 2. "0" == 0
// 3. "0" -> 0
// 4. 0 == 0 // true
"0" == NaN; // false

// Steps:
// 1. "0" -> 0 (string to number)
// 2. 0 == NaN (anything equals to NaN => false)
// NaN is not equal to anything
"0" == ""; // false

// Steps:
// 1. no coercion as both strings
// 2. both strings are not same so false
false == null; // false
false == undefined; // false
0 == null; // false
0 == undefined; // false

"" == null; // false
"" == undefined; // false
"" == NaN; // false

// null and undefined only compare with themselves. Not with anything else. So false in this scenarios as well.
false == NaN; // false

// Steps:
// 1. false -> 0
// 2. 0 == NaN // false
// NaN is not equal to anything
false == "";

// Steps:
// 1. false -> 0
// 2. 0 == ""
// 3. "" -> 0
// 4. 0 == 0 // true
false == {}; // false

// Steps:
// 1. false -> 0
// 2. 0 == {}
// 3. {} toPrimitive "[object Object]"
// 4. 0 == "[object Object]"
// 5. "[object Object]" -> NaN
// 6. 0 == NaN
// Anything equals to NaN is false.

"" == NaN; // false
0 == NaN; // false
"foo" == ["foo"]; // true

// Steps:
// 1. ["foo"] convert to primitive
// 2. toString() converts it to "foo"
// 3. "foo" == "foo" // true

Refer this JS Comparisons Table for all possible combinations.


Thank you for reading long article about JS types and coercion in details.

Connect with me on X or LinkedIn, if you have any feedback or suggestion.


Share this post on:

Previous Post
Power of "this" keyword in JavaScript