Skip to content
academia.sh

Lesson 06 / 21

Primitive Types

The seven primitive types and the distinction from objects; the single number type's floating-point representation, special number values, string, boolean, the two absence values, and symbol.

Contents

The previous topic established how names are declared and resolved. This topic takes up the values names are bound to. The Basic Data Types lesson in the Programming Fundamentals course defined a type as “a set of values and the operations defined on those values.” This lesson applies that definition to JavaScript’s value model.

The model is small: seven primitive types and one object class. The price of that smallness is the weight each type carries in its decisions. For example, there is a single number type and no separate type for integers; this one decision sets the topic for several lessons ahead.

Seven Primitive Types and Objects

A value is either primitive or an object. Primitive values cannot have their content changed and do not carry properties on their own. Objects are sets of properties, and their content can be changed.

The typeof operator gives a value’s type as text:

console.log(typeof 21.5);
console.log(typeof "A1");
console.log(typeof true);
console.log(typeof undefined);
console.log(typeof null);
console.log(typeof 9007199254740993n);
console.log(typeof Symbol("measurement"));
console.log(typeof { station: "A1" });
console.log(typeof [1, 2]);
console.log(typeof function () {});
number
string
boolean
undefined
object
bigint
symbol
object
object
function

The output does not correspond to the type list one to one at two points. In the fifth line, null is a primitive value, but the result is object; this is the trace of the backward compatibility discussed in the Language Versions and Standardization lesson. In the last line, function is not a separate type; functions are callable objects, and typeof reports this with a distinct value.

The seven primitive types are: number, BigInt, string, boolean, undefined, null, and symbol. This lesson’s subject is six of these; BigInt is the next lesson’s subject.

Number: A Single Type

JavaScript has no separate types for integers and real numbers. The whole of the number type is the IEEE 754 double-precision representation defined in the Floating-Point Numbers lesson of the How Computers Work course: 64 bits, 1 sign bit, 11-bit biased exponent, 52-bit stored mantissa.

This produces two direct consequences.

First, writing an integer literal does not produce a separate type:

console.log(Number.isInteger(23));
console.log(23 === 23.0);
true
true

23 and 23.0 are the same value; both correspond to the same bit pattern. This is why the first lesson’s Math.max result was written as 23 — a floating-point number whose fractional part is zero is written without showing the fraction when converted to text.

Second, decimal values that cannot be written as a finite binary fraction are held approximately:

console.log(0.1 + 0.2);
console.log(0.1 + 0.2 === 0.3);
0.30000000000000004
false

This result is not a surprise, it is a direct consequence of the representation. The model from the Floating-Point Numbers lesson applies here exactly: the values 0.10.1 and 0.20.2 cannot be written as a finite binary fraction, each is rounded to the nearest representable value, and the addition is performed on these rounded values. The result falls not on 0.30.3’s representation, but on another value closest to it.

The practical rule established in the same lesson applies here too: floating-point values are compared not with equality but with tolerance. Quantities requiring precision, like money, are kept as integers carrying the smallest unit.

The safe range of integers also comes from the mantissa’s width. Because the mantissa provides 53 bits of precision, integers whose absolute value exceeds 25312^{53}-1 become indistinguishable from one another:

console.log(Number.MAX_SAFE_INTEGER);
console.log(9007199254740992 === 9007199254740993);
9007199254740991
true

Two different integers coming out equal is because both round to the same representable value. Cases where this boundary is crossed are the next lesson’s subject.

Special Number Values

The representation carries three special values, and all three are directly visible in the language:

console.log(1 / 0, -1 / 0, 0 / 0);
console.log(Object.is(0, -0), 1 / -0);
Infinity -Infinity NaN
false -Infinity

Division by zero does not throw an error; it produces an infinity value. Undefined operations give NaN (not a number). NaN is a number value and its type is number; its name says that what it represents is not a number.

Signed zero is also part of the representation: 0 and -0 are different bit patterns but are considered equal in equality comparisons. Object.is is the operation that sees the distinction. The -0 value is invisible in most computations; it only becomes significant because it determines the sign in division by zero.

String

The string type carries text. Strings are written with single quotes, double quotes, or backticks; there is no behavioral difference between the first two. The backtick form has separate capabilities and will be covered in the Strings lesson.

const name = "measurement";
console.log(name.length);
console.log(name[0]);
console.log(name.toUpperCase());
console.log("A1".codePointAt(0));
11
m
MEASUREMENT
65

String length is not the character count, it is the count of 16-bit code units. The surrogate pair concept defined in the Character Encodings lesson of the How Computers Work course becomes visible here: a code point outside the basic multilingual plane is represented by two code units, and length counts it as two.

console.log("😀".length);
2

This is the key to understanding the string type: a string is not a sequence of code points, it is a sequence of code units. The distinction changes the result in character counting and slicing operations.

Boolean

The boolean type has two values: true and false. They are produced by comparisons and are the input of conditional constructs. JavaScript also considers non-boolean values usable in a condition; which value is considered true and which is considered false is the subject of the Truthy and Falsy Values lesson.

Two Absence Values

Most languages have a single absence value. JavaScript has two, and their meanings are separate.

undefined is a value never having been given at all. A declared but unassigned variable, a nonexistent object property, and a parameter with no corresponding argument carry this value. The language produces it on its own.

null is a deliberately placed absence. The language does not produce it on its own; the program’s author assigns it to say “there is no value here.”

const record = { station: "A1", temperature: 21.5 };
console.log(record.humidity);
console.log("humidity" in record);

let nextReading;
console.log(nextReading);

const calibration = null;
console.log(calibration);
undefined
false
undefined
null

The distinction is directly useful in measurement data. If a record’s humidity field does not exist at all, reading it gives undefined; if the field exists but the sensor produced no value, writing null preserves the information “not measured.” The two are not the same thing: the first concerns the data’s shape, the second concerns the data’s content.

Symbol

Symbol is the primitive type that produces a unique value on every call. Two symbols produced with the same description text are not equal to each other:

console.log(Symbol("source") === Symbol("source"));
false

Its use case is adding properties to objects with no risk of collision. Symbol-keyed properties are invisible in ordinary key listings and in serialization:

const source = Symbol("source");
const record = { station: "A1", temperature: 21.5, [source]: "local file" };
console.log(Object.keys(record));
console.log(JSON.stringify(record));
console.log(record[source]);
[ 'station', 'temperature' ]
{"station":"A1","temperature":21.5}
local file

The provenance information added to the measurement record was carried without polluting the record’s data content. Symbols’ second role in the language — adapting objects’ specific behaviors — is the subject of the next course.

Primitive Values Are Immutable

A primitive value’s content cannot be changed. Attempting an indexed assignment on a string throws an error in strict mode:

const name = "A1";
try {
  name[0] = "B";
} catch (error) {
  console.log(error.name + ": " + error.message);
}
console.log(name);
TypeError: Cannot assign to read only property '0' of string 'A1'
A1

The same operation throws no error in a script not in strict mode; it is silently ignored, and the string still does not change. This is the second example of the strict-mode difference discussed in the Ways to Run Code lesson: the behavior is the same, the feedback is different.

This is why string methods never modify the original value, they always produce a new string. The immutable–mutable distinction from the Programming Fundamentals course is strict here by type: all seven primitive types are immutable, objects are mutable.

The Measurement Record’s Types

The data that is this course’s axis is a combination of these types:

const record = {
  station: "A1",
  temperature: 21.5,
  humidity: null,
  verified: false,
};

for (const key of Object.keys(record)) {
  console.log(key, "->", typeof record[key]);
}
console.log("missing field:", typeof record.pressure);
station -> string
temperature -> number
humidity -> object
verified -> boolean
missing field: undefined

The third line is a reminder: because the humidity field carries null, the typeof result comes out as object. Whether a field is truly empty is tested not with typeof but with a direct comparison. Which rule these comparisons follow is the subject of the Equality Comparisons lesson.

Summary

  • Values are either primitive or objects; there are seven primitive types and all of them are immutable.
  • typeof null gives object, and functions give function; neither corresponds to the type list one to one.
  • The single number type is the IEEE 754 double-precision representation: 23 and 23.0 are the same value, decimal fractions are approximate, and the safe integer range is bounded by 25312^{53}-1.
  • Infinity, -Infinity, NaN, and -0 are part of the representation; NaN’s type is number.
  • String length is the code unit count, not the character count.
  • undefined denotes a value never given, null a deliberately placed absence.

Next Step

Two different integers above the safe integer range coming out equal is the one point this lesson leaves unresolved. The next lesson takes up the cases where this boundary is actually crossed and the language’s answer to it: a separate primitive type carrying an integer of arbitrary size, its behavior in operators, and the error that arises when it is mixed with the number type.

To keep your progress and take notes, Log in

My notes

Log in to take notes.

Start typing to search.

↑↓ Esc navigate · open · close