The previous page, Statements, variables and flow, left a question open: a form field's '5' added to a total of 0 gives '05'. The answer is in section 4, and it depends on the types and conversion tables that come before it. Remember the Prof's law of "weird stuff": focus on the constructs in a new language that are odd to you, because they show how it varies from what you know from Java or C and C++. Types are where JavaScript has the most of them.
1. The primitive types
JavaScript has seven primitive types: undefined, null, boolean, number, BigInt, string and Symbol. Everything else is an object, including arrays and functions. A primitive is a single immutable value. Objects and arrays covers the other side of that line.
typeof reports for it. null is the one that does not match.Strings
A string is written in single quotes, double quotes or backticks. "Thomas", 'x' and 'Who are you?' are all the same type, and JavaScript has no separate character type. A string may include special escaped characters and formatting characters.
ES6 added template strings, which use backticks. A ${ } inside one holds any expression, and its value is converted to a string in place.
Numbers and BigInt
Numbers such as 5, -345, 56.7 and -456.45677 are all one type. There is no separate integer type, and numbers in JavaScript tend not to be complex, in the sense of higher math. Each is a 64-bit floating-point value, the same as a double in Java or C, so the usual rounding appears. Integers are exact only up to Number.MAX_SAFE_INTEGER, which is 9007199254740991.
BigInt is the type for integers beyond that. A BigInt literal ends in n. It has been available across browsers since September 2020, according to MDN. Arithmetic does not mix a BigInt with a number, so you have to convert one of them yourself.
Booleans and symbols
A boolean is true or false. A symbol is a newer type for unique values. Every call to Symbol() makes a new one, even with the same description, so a symbol can name a property without any chance of colliding with another name. You will meet the built-in ones, such as Symbol.iterator, which is how for/of finds out how to walk a collection.
undefined and null
JavaScript has two values for nothing, each the only value of its own type. Consider a variable's lifecycle to remember which is which. It starts as undefined, because nothing has been assigned. It holds values for a while. At the end, a program sets it to null to say on purpose that it holds nothing. So undefined usually comes from the language, and null from a programmer.
Asking with typeof
The typeof operator returns a value's type as a string. Two answers are odd. typeof null is 'object', which is a historical quirk and does not make null an object. MDN traces it to the first implementation, where objects had a type tag of 0 and null was the null pointer, and a proposed fix was rejected. To test for null, write value === null. An array also reports 'object', so test with Array.isArray(). Unlike any other use of an unknown name, typeof on a name that was never declared returns 'undefined' and does not throw, although it does throw on a let read before its declaration.
2. Weak typing
Two separate properties get called weak typing, and JavaScript has both. It is dynamically typed: a variable has no declared type, so one holding a string can later hold a number.
Some languages are more strongly typed, in that you must declare the type of a variable and stick with it. The next sample imagines that syntax in JavaScript, and it is close to how TypeScript writes it.
JavaScript is also weakly typed in the narrower sense: an operator given the wrong type converts its operands and carries on, where a stronger language stops. Python is dynamically typed too, but 1 + '5' there is a TypeError. In JavaScript it is '15'. Implicit conversion of this kind is called coercion, and most of this page is about it.
While weak typing seems beneficial to a programmer's thinking at first, it leads to confusion and errors. Two questions to consider as you read: would you prefer errors at compile or interpret time, or at run time? And do you think weak typing makes web apps safer?
The big concern is not knowing what things do. && and || do not behave as they do in other languages, toString() on objects and arrays has details to learn, and an occasional implementation difference adds to it. Together these lead to an assumption of craziness, which Gary Bernhardt's short talk Wat plays for laughs. By the end of this page you should be able to explain its JavaScript examples.
3. The conversion tables
Three conversions account for nearly all the coercion you will see: to boolean, to number and to string. The specification names them ToBoolean, ToNumber and ToString. These tables give the results, and the sections after them say when each one runs.
To boolean
| Original type | Converted to a boolean |
|---|---|
undefined | false |
null | false |
| Number | false if 0, -0 or NaN, else true |
| BigInt | false if 0n, else true |
| String | false if the string's length is 0, else true |
| Symbol | true |
| Object | true, every time |
A value that converts to false is called falsy, and anything else truthy. There are few enough falsy values to memorize: false, 0, -0, 0n, NaN, '', null and undefined. Three truthy values catch people: '0' and ' ' are strings with a length, and [] is an object. (The browser has one falsy object, the old document.all, kept for compatibility.)
To number
| Original type | Converted to a number |
|---|---|
undefined | NaN |
null | 0 |
| Boolean | 1 if true, 0 if false |
| String | The numeric value, if the whole string is a number. An empty or whitespace-only string is 0. All other strings become NaN. |
| Object | The object becomes a primitive first, then this table applies to that. A plain object becomes a string, so NaN. |
| Array | The array becomes a string by joining its elements with commas, then this table applies to the string. |
| BigInt | Number(1n) is 1. Implicit conversion, as in +1n, throws a TypeError. |
| Symbol | Throws a TypeError. |
The array row explains results that otherwise look arbitrary. An empty array becomes '', which is 0. An array of one element becomes that element as a string, so [5] is 5, and this works recursively. Two or more elements give a string with a comma in it, which is NaN. Inside an array, null and undefined join as an empty string, so [undefined] is 0 even though undefined alone is NaN, and [true] is NaN because the string 'true' is not a number.
To string
| Original type | Converted to a string |
|---|---|
undefined | 'undefined' |
null | 'null' |
| Boolean | 'true' if true, 'false' if false |
| Number | A string representation of the numeric value, such as '5'. This may include the special values 'NaN', 'Infinity' or '-Infinity'. Note that -0 gives '0'. |
| BigInt | The digits, with no n. |
| Object | The value of the object's toString() method, in most cases. An array gives its elements joined with commas, and a plain object gives '[object Object]'. An object with no way to become a primitive throws a TypeError. |
| Symbol | Throws a TypeError when converted implicitly. String(symbol) works, as a special case. |
The object and array rows of the number table, and the object row here, depend on the same step, called ToPrimitive. Asked for a primitive, an object tries valueOf() and then toString(), and uses the first one that returns a primitive. The default valueOf() returns the object itself, which does not count, so for arrays and plain objects toString() decides. (When a string is what's wanted, the order is reversed. For these objects the result is the same.)
This playground runs the tables. Each line calls an explicit conversion, which applies the same rules that coercion does, and JSON.stringify keeps the quotes visible on strings.
4. The + operator, and the others
You should get familiar with this use of +, because it serves both as addition and as string concatenation, and that double job makes it different from every other arithmetic operator. Here is what the specification does for a + b.
- Convert each side to a primitive, so objects and arrays become strings.
- If either primitive is a string, convert both to strings and concatenate them.
- Otherwise, convert both to numeric values (number or BigInt) and add them.
That answers the question from the previous page. In 0 + '5' the right side is a string, so the 0 becomes '0', and the result is '05'. A form field's value is always a string, so adding one to a running total joins them. Convert the value to a number first, as section 8 shows.
The third rule means that with no string in sight, + is numeric, even when neither operand is a number. true + true is 2, and null + 1 is 1. Objects do reach concatenation, through the first rule, because they usually become strings.
+ has a string branch. -, *, / and % always work on numeric values.Subtraction has no such choice. If either of the two values isn't numeric, it converts them to numeric values (numbers, unless BigInt is involved) and subtracts. The multiplication, division and remainder operators all behave the same way as subtraction. So the same two operands can give a string from + and a number from anything else. These operations can lead to some initially funky situations, so be careful with how you are using them, and test often.
The last two lines depend on order. + runs left to right, so 1 + 2 + '3' adds first and then joins, and '1' + 2 + 3 joins twice.
One Wat example needs a note on parsing. Written as an expression, [] + {} and {} + [] both give '[object Object]'. Typed at the start of a statement in a console, {} + [] can give 0, because the parser reads {} as an empty block, which leaves the unary expression +[]. Conversion is doing what the tables say. The difference comes from the grammar.
For building text, a template string avoids the question, since it has no second meaning. The two lines below give the same string.
5. What &&, || and ?? return
Careful: these operators do not work the way you might believe. Try 5 && 10. In theory, && would return true if both the left and right expressions are true, but this is only somewhat true. The result here is 10.
Given expr1 && expr2, if expr1 can be converted to true, it returns expr2. Otherwise, it returns expr1. Given expr1 || expr2, if expr1 can be converted to true, it returns expr1. Otherwise, it returns expr2. Note that each returns the value of the expression, not the truthy value. The conversion to boolean is used for the decision and then thrown away. MDN has the detail under logical AND and logical OR.
Both also short-circuit. If the first expression of && is falsy, the second is never run at all, and similarly if the first expression of || is truthy. All of this is fine in an if() statement, since the if() will evaluate the returned expression on its own. It gets tricky when doing assignments, where the operand itself lands in the variable.
a, b does not run.The line with [] || ['1'] keeps the empty array, because an array is an object and every object is truthy. The older idiom for a default value, name = name || 'Anonymous', has a gap for the same kind of reason. It replaces every falsy value, so an empty string, a 0 or a false that someone set on purpose is lost.
The nullish coalescing operator
The nullish coalescing operator ?? fills that gap. It returns the first value if it !== null and !== undefined, and otherwise returns the second value. That is useful when you want to guarantee that a variable has a defined value. It is not to be confused with ||, which returns the first value if it is not falsy. MDN lists ?? as available across browsers since July 2020.
Mixing ?? directly with && or || is a SyntaxError. Add parentheses to say which you mean, as in (a || b) ?? c.
The third logical operator, !, is the plain one. It converts its operand to a boolean and inverts it, so it always returns a boolean.
6. == and ===
JavaScript is illogical, it would seem. In the sample below, 2 is not equal to true, and yet an if treats 2 as true.
JavaScript has logic, but operators can get a bit hairy if you don't know how they compare things. An if uses the boolean table. == does not, and it has its own algorithm in which a boolean becomes a number, so 2 == true is really 2 == 1. Reading the spec from time to time is good for your health, if only mentally.
The == algorithm
To spare you some time, here is the specification's IsLooselyEqual, in order. != works the same way, with the return value logically notted.
In plainer terms:
- If they are both objects, return
trueonly if they reference the same object. Two arrays with the same contents are not equal. - When
nullandundefinedare compared, returntrue. Neither one equals anything else. This is whynull == 0isfalse, althoughNumber(null)is0: no rule convertsnull. - If they are of different types and one is a number, convert the other to a number, for a string or a boolean. A boolean is converted even when the other side is not a number.
- If an object is compared with a primitive, convert the object to a primitive using
valueOf()ortoString(). - Primitives of the same type are compared the way you would expect, except that if
NaNis involved it is alwaysfalse, and+0and-0are treated as the same.
Walk through [] == false, which looks as if it should be false twice over, since [] is truthy. The right side is a boolean, so it becomes 0, leaving [] == 0. The left side is an object, so it becomes the primitive '', leaving '' == 0. That is a string against a number, so the string becomes 0, leaving 0 == 0, which is true. At no point did the algorithm ask whether [] was truthy, and it does not ask that about the 2 in 2 == true either.
The Type explorer below shows these steps for any pair from a list of awkward values, for ==, ===, +, -, &&, || and ??. It also has a matrix of == across every pair, and typeof, Number(), String() and Boolean() for a single value. JavaScript computes each result as you choose. It is also available as a page of its own in Resources › Tools.
=== and !==
You may also come across equality operators that have an extra equals sign on the end. This signifies a strict evaluation. The only difference is that strict evaluation doesn't perform any type conversions. Outside of that it follows the same comparison, and in fact the first rule of == hands a same-typed pair straight to it. Different types give false, every time.
This course's position: write === and !==, because you can read them without running the algorithm in your head. Convert explicitly first if the types may differ. The one == you will often see on purpose is value == null, which is true for both null and undefined and for nothing else.
Comparisons with < and >
The comparison operators >, <, >= and <= convert as well, by their own rule, and they have no strict form. Two strings compare as text, character by character. Otherwise both sides become numbers. So '10' < '9' is true and '10' < 9 is false. They also convert null to 0, where == does not, which gives an odd trio of results.
7. NaN and Infinity
The number type has two special values that come out of arithmetic where a language like Java would throw.
NaN
The value of NaN (Not a Number) can seem a bit odd. Using a value that is not a number in arithmetic can result in this value. Don't compare NaN, though, because it is not equal to anything, itself included. Use isNaN() instead. And yes, its type is number.
The last group shows why two versions exist. The global isNaN() converts its argument to a number first, so it reports true for the string 'hello', which is not the value NaN. Number.isNaN() converts nothing, and it is true only for NaN itself. Prefer it when you are asking about a value that has already been through a calculation.
Infinity
Getting to Infinity is pretty easy in JavaScript: just divide by zero. You also can get there pretty quickly by exceeding bounds. Once you are at Infinity it is toxic, with no turning back through ordinary addition or subtraction.
There are exceptions to "toxic". Dividing by it gives 0, and Infinity - Infinity is NaN. Although not bounded, Infinity is a number, as typeof verifies. Unlike NaN, you can compare Infinity and -Infinity, and they work as expected. Since Infinity is still like an error case, a built-in function, isFinite(), checks that a value is finite. Note that it is false for NaN too. Like isNaN(), it converts first, and Number.isFinite() is the version that does not.
8. Converting on purpose
While JavaScript will go ahead and convert the values for you with no hassle, converting them manually can lead to more predictable development. You will see all of these in other people's code, so learn to read them.
| Scheme | Explanation |
|---|---|
+x | Used as a unary operator, + converts a value to a number. It throws on a BigInt. |
Number(x) | Converts a value to a number if possible, following the table in section 3. It also accepts a BigInt. |
parseInt(s), parseFloat(s) | Parse an integer or a float out of the start of a string. They ignore trailing characters. |
Boolean(x) | Converts a value to a boolean, following the table in section 3. |
!!x | The first not converts the value to a boolean and inverts it. The second not inverts the first. |
x + '' | Given what + does with a string, this often serves to convert a value to a string. It throws on a symbol. |
x.toString() | A method that returns a string form of the value. For objects you may see '[object Object]' or a user- or host-defined value. It throws on null and undefined, which have no methods. |
String(x) | Converts any value to a string, including null, undefined and symbols. |
The difference between Number() and parseInt() affects form input most. Number() takes the whole string or gives up, except that an empty string is 0. parseInt() reads as far as it can and stops, and it reports nothing to read as NaN. So an empty field looks like a zero to one and a failure to the other, and neither is the right answer until you have decided what an empty field means in your program.
The second argument to parseInt() is the radix. Pass 10 unless you mean another base. Since conversion is so much of what can go wrong in a weakly typed language, a check of the type before you act on a value is a normal thing to write.
9. The operators, and their order
Expressions are made with operators, and for most of them only the conversion rules above are unusual. Here is the set, for reference.
| Group | Operators |
|---|---|
| Basic arithmetic | + addition, - subtraction and unary negation, / division, * multiplication, % remainder, ** exponent |
| Increment and decrement | ++ add one, -- subtract one |
| Comparison | >, <, >=, <=, != not equal, == equal, !== strict inequality, === strict equality |
| Logical | && and, || or, ! not, ?? nullish coalescing |
| Bitwise | &, |, ^, ~, and the shifts >>, >>> and << |
| String | + concatenation |
% is often called modulus, but MDN is clear that it is a remainder. The result takes the sign of the dividend, so -13 % 5 is -3, where a true modulo would give 2. Watch for this if you index into something with a value that can go negative.
The bitwise and shift operators are not commonly used in JavaScript traditionally, but with the rise of 3D and memory access they could be more common. A number is converted to a 32-bit integer for these operations, which is different from anything else in the language.
Precedence
Just like in math, JavaScript has an order of operations for its operators, and parentheses are the highest.
MDN's precedence table shows every operator and where it falls. It currently numbers its levels from 18, for grouping, down to 1, for the comma. Those numbers belong to the table, and the language's specification has no such numbering, since its grammar implies the order. So learn the relative order and do not memorize a count. The spread syntax is typically not regarded as an operator, and MDN includes it in the table anyway to show how tightly it binds.
A few relative positions come up often. Multiplication comes before addition. Both come before the comparisons, and those come before ==. Then && comes before ||. Assignment is near the bottom, so the right side is worked out before it is stored. Most operators of the same level run left to right, as the 1 + 2 + '3' example showed. ** and assignment run right to left.
You will not hold the whole table in your head, and a later reader won't either. Where the order isn't obvious, add parentheses.
10. Summary
| Concept | Key points |
|---|---|
| Primitive types | undefined, null, boolean, number, BigInt, string, Symbol. Everything else is an object. |
typeof | Returns the type as a string. typeof null is 'object', a historical quirk. Arrays also report 'object'. |
| Weak typing | Variables have no declared type, and operators convert their operands instead of failing. |
| Falsy values | false, 0, -0, 0n, NaN, '', null, undefined. All objects are truthy, including []. |
| To number | '' and whitespace are 0, other non-numeric strings are NaN, null is 0, undefined is NaN. Arrays go through a string first. |
+ | Each side to a primitive. If either is a string, concatenate. Otherwise add as numbers. 0 + '5' is '05', and true + true is 2. |
-, *, /, % | Always convert to numeric values. % is a remainder, with the sign of the dividend. |
&&, ||, ?? | Return an operand, not a boolean, and skip the right side when the left settles it. ?? tests only for null and undefined. |
== | A list of rules that convert until the types match. A boolean becomes a number, an object a primitive. null equals only null and undefined. Truthiness is never consulted. |
=== | No conversion. Different types are never equal. This course's choice. |
NaN | Not equal to itself. Test with Number.isNaN(). Its type is number. |
Infinity | Comes from dividing by zero or overflowing. It compares normally. Test with isFinite(). |
| Explicit conversion | Number() takes the whole string, parseInt() reads what it can. Use String() and Boolean(), and read +x, !!x and x + ''. |
| Precedence | Learn the relative order, look up the rest on MDN, and use parentheses where the order isn't obvious. |