completefrontend
Open lab My progress
THE JAVASCRIPT FIELD GUIDE

null inside the engine

Trace JavaScript null through V8 oddballs, typeof history, undetectable maps, document.all, null prototypes, and prototype lookups.

By the end, you can
  • 01
    Explain how V8 stores nullSay why V8 has exactly one null, which ready-made answers it keeps, what its undetectable mark does, and how other engines store null.
  • 02
    Read the instructions behind null checksMatch === null, == null, and typeof x === "object" to the small instructions V8 makes for them, and explain why document.all == null is true.
  • 03
    Use objects with a null prototype safelyKnow when an object with no prototype helps, why V8 stores it in dictionary mode, and how every property lookup stops at null.

One null, made once

You know null from everyday JavaScript. It is the value you write on purpose to say "empty" or "nothing here". This lesson shows how a JavaScript engine stores it. An engine is the program that runs your code. We use V8, the engine inside Chrome and Node.js. Every fact here was checked in Node 22, which uses V8 version 12.4.

Inside V8, there is exactly one null. V8 makes it once, when it starts, and never makes another one. V8 calls a few special built-in values oddballs, and null is one of them. A variable that holds null only stores a pointer to it. A pointer is a memory address: it says where a value is.

The main idea

In V8, null is one oddball object that lives in read-only space: a part of memory that nothing is allowed to change. The object keeps a few ready-made answers:

  • As a number, it is 0.
  • As a string, it is "null".
  • typeof gives "object".

V8 also gives every object a map: V8's record of a value's type and shape. The map of null has a mark called undetectable. This mark helps V8 answer x == null quickly.

If you want the everyday rules for null first, read null for beginners. This lesson also uses the idea of a tagged pointer: a stored pointer with a small mark that says "this is a pointer, not a small number". The Tagged values, Smis & heap numbers lesson explains it. This lesson comes right after undefined inside the engine.

Real-life analogyOne shared empty label

A shop keeps one printed "empty" label. Any box with nothing in it points to that label. Nobody prints a new label for each box, and nobody can change the words on it.

In real life: There is one printed "empty" label
In JavaScript: There is one null in V8
In real life: Many boxes point to that same label
In JavaScript: Every null in your code is a pointer to that one value
In real life: The words on the label are printed and can't change
In JavaScript: null keeps fixed answers for number, string, and typeof

Where V8 keeps null and its answers

V8 is written in the C++ language, and its source code is public. So we can read how it builds null. The file oddball.h describes oddballs. Its comment says that four values are oddballs: null, undefined, true, and false.

Each oddball keeps a few ready-made answers, so V8 never has to work them out again. V8 calls these saved answers cached fields:

  • to_number_raw and to_number: the value as a number.
  • to_string: the value as a string.
  • type_of: what typeof gives.
  • kind: a small number that says which oddball this is. For null, it is kNull = 3.
V8's notes on oddball fields (C++ source)Text
// V8 12.4.254.21, src/objects/oddball.h// The Oddball describes objects null, undefined, true, and false.// [to_number_raw]: Cached raw to_number computed at startup.// [to_string]: Cached to_string computed at startup.// [to_number]: Cached to_number computed at startup.// [typeof]: Cached type_of computed at startup.static constexpr uint8_t kNull = 3;static constexpr uint8_t kUndefined = 4;

Another file, setup-heap-internal.cc, builds null when V8 starts. Here is what the lines below do:

  • Line 2 makes a separate map just for null, called null_map.
  • Line 4 puts the undetectable mark on that map.
  • Lines 5 and 6 fill in the ready-made answers: the string "null", the number zero, the typeof answer "object", and the kind kNull. Smi::zero() is the number 0 as a Smi, V8's name for a small whole number.
How V8 builds null when it starts (C++ source)Text
// V8 12.4.254.21, src/heap/setup-heap-internal.ccALLOCATE_AND_SET_ROOT(Map, null_map, Map::kSize);InitializePartialMap(null_map, meta_map, ODDBALL_TYPE, sizeof(Null));roots.null_map()->set_is_undetectable(true);Oddball::Initialize(isolate(), factory->null_value(), "null",                    handle(Smi::zero(), isolate()), "object", Oddball::kNull);
Ask Node how it stores null (Node only)JavaScript
%DebugPrint(null);

%DebugPrint is a special V8 helper. It prints what V8 stores for a value. It only works when you start Node with node --allow-natives-syntax, so this block can't run on this page. The lesson's test runs it and checks the parts that are the same on every run:

The parts of the output that never changeText
DebugPrint: ... [Oddball] in ReadOnlySpace: #null... [Map] in ReadOnlySpace - type: ODDBALL_TYPE - stable_map - undetectable - non-extensible - prototype: ... <null>
  • [Oddball] in ReadOnlySpace: #null: it is the null oddball, kept in read-only space.
  • type: ODDBALL_TYPE: the map says its type is oddball.
  • undetectable: the map has the undetectable mark.
  • prototype: ... <null>: the map's prototype is null itself.

The test skips memory addresses and object sizes, because they change from build to build. This Node build has pointer compression turned off: a trick that stores pointers in fewer bytes. It also has the sandbox turned off: a fence that limits which memory V8 can touch. Chrome usually turns both on and may use a newer V8.

null's ready-made answers in real codePop out in the code editor (opens in a new tab)JavaScript
console.log(Number(null));console.log(String(null));console.log(null + 1);console.log(null >= 0);console.log(null == 0);

Here is what each line prints:

  • Line 1 prints 0: null as a number.
  • Line 2 prints null: null as a string.
  • Line 3 prints 1. The + turns null into 0 first, and 0 + 1 is 1.
  • Line 4 prints true. The >= also turns null into 0, and 0 >= 0 is true.
  • Line 5 prints false. Loose equality, ==, has its own rule for null: it is only loosely equal to null and undefined. So null == 0 is false.
One null card on the left, made once. Arrows lead to three answers it keeps: as a number it is 0, as a string it is null, and typeof gives object.nullmade once, read-onlyready-made answersnumber: 0string: "null"typeof: "object"
V8 makes one null when it starts and keeps three answers on it. So Number(null), String(null), and typeof null just read a stored answer.

Other engines store null in different ways. Read each row of the table as: the engine, how it stores null, and what its source code says.

How each engine stores null
EngineHow it stores nullWhat its source code says
V8, for nullOne read-only object, an oddball, with the kind kNull = 3.It saves 0 as the number, "null" as the string, and "object" for typeof.
V8, for undefinedA different read-only oddball, with its own map.Its typeof answer is "undefined". Its map has the undetectable mark too.
JavaScriptCore (Safari)A fixed value stored right inside the value. No object in memory.JSCJSValue.h says ValueNull = OtherTag, and uses it for JSNullTag.
SpiderMonkey (Firefox)A value with a type mark that says "null".Value.h gives the type number JSVAL_TYPE_NULL = 0x04 and the mark JSVAL_TAG_NULL.
Where the other engines name nullText
JavaScriptCore JSCJSValue.h:  ValueNull = OtherTag; JSValue(JSNullTag) stores ValueNull. SpiderMonkey js/public/Value.h:  JSVAL_TYPE_NULL = 0x04; JSVAL_TAG_NULL stores a null payload. V8 12.4 oddball.h:  Null is an Oddball subclass; kind kNull = 3.

JavaScriptCore is the engine inside Safari. Its file JSCJSValue.h stores null as a fixed value, ValueNull, right inside the value itself. No object in memory is needed. SpiderMonkey is the engine inside Firefox. Its file Value.h gives null the type number JSVAL_TYPE_NULL = 0x04. Every engine gives the same JavaScript results. Only the way it stores null is different.

Why typeof null says "object"

The rules of JavaScript, called the spec, still say that typeof null gives "object". The reason is older than V8. It goes back to the first JavaScript engine, built at Netscape in 1995.

That engine stored every value in one 32-bit word: a number made of 32 bits, where each bit is a 0 or a 1. The last 3 bits were a type tag, a small code that said what kind of value the word held. Axel Rauschmayer's archived 2ality article lists the old tags:

  • 000: an object.
  • 1 as the last bit: a whole number.
  • 010: a decimal number.
  • 100: a string.
  • 110: a boolean.

null was stored as a word where all 32 bits are 0. The old Netscape source shows this in jsapi.h: JSVAL_OBJECT is 0x0, and JSVAL_NULL is OBJECT_TO_JSVAL(0). In jsapi.c, the old typeof code checks for the object tag, JSVAL_IS_OBJECT, before it checks for numbers, strings, or booleans. All those zeros look like the object tag, so the answer was "object".

A small model of the old tag checkPop out in the code editor (opens in a new tab)JavaScript
const JSVAL_OBJECT = 0x0;const JSVAL_INT = 0x1;const JSVAL_STRING = 0x4;const JSVAL_BOOLEAN = 0x6;const JSVAL_VOID = 0xfffffff0; function tagBits(word) {  return (word & 0x7).toString(2).padStart(3, "0");} function typeof1995(word) {  const tag = word & 0x7;  if (word === JSVAL_VOID) return "undefined";  if (tag === JSVAL_OBJECT) return "object";  if (word & JSVAL_INT) return "number";  if (tag === JSVAL_STRING) return "string";  if (tag === JSVAL_BOOLEAN) return "boolean";  return "number";} const nullWord = 0x00000000;console.log("null", tagBits(nullWord), typeof1995(nullWord));console.log("object", tagBits(0x00001000), typeof1995(0x00001000));console.log("string", tagBits(0x00003004), typeof1995(0x00003004));

This code is a teaching model, not the real 1995 engine. It turns each value into a 32-bit word and reads the last 3 bits. In plain words, line by line:

  1. Line 1 sets the object tag to 0.
  2. Line 7 starts tagBits. It keeps only the last 3 bits, with word & 0x7, and shows them as 0s and 1s.
  3. Line 11 starts typeof1995, the old typeof decision.
  4. Line 14 gives "object" when the tag is 000. Nothing checks for null before it.
  5. Line 21 makes the word for null: all zeros.
  6. Line 22 prints null 000 object.
Step through the old 1995 typeof null check
Step 0 of 4Ready
Your turn: follow the blue line

Step through a small model of how the 1995 engine decided typeof. It is teaching code, not how engines store values today.

Running in
  1. script
Next: line 21
const JSVAL_OBJECT = 0x0;const JSVAL_INT = 0x1;const JSVAL_STRING = 0x4;const JSVAL_BOOLEAN = 0x6;const JSVAL_VOID = 0xfffffff0; function tagBits(word) {  return (word & 0x7).toString(2).padStart(3, "0");} function typeof1995(word) {  const tag = word & 0x7;  if (word === JSVAL_VOID) return "undefined";  if (tag === JSVAL_OBJECT) return "object";  if (word & JSVAL_INT) return "number";  if (tag === JSVAL_STRING) return "string";  if (tag === JSVAL_BOOLEAN) return "boolean";  return "number";} console.log("null", tagBits(nullWord), typeof1995(nullWord));console.log("object", tagBits(0x00001000), typeof1995(0x00001000));console.log("string", tagBits(0x00003004), typeof1995(0x00003004));
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.
Three rows from the old model. null is stored as 0x00000000, its last 3 bits are 000, so old typeof says object. object is 0x00001000, bits 000, object. string is 0x00003004, bits 100, string.valuestored wordlast 3 bitsold typeofnull0x00000000000objectobject0x00001000000objectstring0x00003004100string
The old engine looked at the last 3 bits of each stored word. 000 meant object, and null was all zeros, so typeof null said "object".

Could the answer be fixed? Brendan Eich, who created JavaScript, suggested a change on the es-discuss mailing list. An old proposal said: "With opt-in to Harmony, typeof null === "null"." Harmony was the working name for the next big version of JavaScript. The proposal is archived at harmony:typeof_null. It was dropped, because changing the answer would break websites that already depend on "object".

The small instructions behind null checks

Before V8 runs a function, it turns the function into bytecode: a list of small, simple instructions. V8's interpreter, called Ignition, then runs those instructions one by one. Each instruction has a short name, such as TestNull.

The lesson's test runs the code below with node --print-bytecode, which prints the bytecode of each function. It checks only the instruction names. It skips their positions, because those can change.

Three functions, three kinds of null checkJavaScript
function strictNull(x) {  return x === null;} function looseNull(x) {  return x == null;} function objectType(x) {  return typeof x === "object";} console.log(strictNull(null));console.log(looseNull(undefined));console.log(objectType(null));
The instruction names Node printedText
strictNull: Ldar a0; TestNull; ReturnlooseNull: Ldar a0; TestUndetectable; ReturnobjectType: Ldar a0; TestTypeOf #7; Return

In each function, Ldar a0 loads the first argument, and Return gives back the result. The instruction in the middle is the check itself:

x === null becomes TestNull. It checks whether the value is the one null by comparing pointers.

x == null becomes TestUndetectable. It checks the value's map for the undetectable mark. V8's interpreter generator file describes this check as "null, undefined or document.all".

typeof x === "object" becomes TestTypeOf #7. The #7 picks the "object" test. In that test, null gives true first, before V8 looks at the map of any other object.

The undetectable mark and document.all

document.all is a very old browser feature. It lists every element on a web page. Browsers keep it working for old websites, but they make it look missing in checks. The undetectable mark is what links == null and document.all.

A comment in V8's map.h file says that an object with the undetectable mark:

  • gives "undefined" for typeof,
  • turns into false as a boolean,
  • is useful for document.all.

V8's public API is the set of tools that programs such as Chrome use to build on V8. In api.cc, it has ObjectTemplate::MarkAsUndetectable. A browser can use it to put the mark on its own objects.

Test an object with the undetectable mark (Node only)JavaScript
const value = %GetUndetectable();console.log(typeof value);console.log(value == null);console.log(value == undefined);console.log(value === null);console.log(Boolean(value));%DebugPrint(value);

%GetUndetectable() is another special V8 helper. It makes a test object that has the undetectable mark. The lesson's test checks that this object:

  • gives "undefined" for typeof (line 2),
  • is loosely equal, ==, to null and to undefined (lines 3 and 4),
  • is not strictly equal, ===, to null (line 5),
  • is falsy, so Boolean(value) is false (line 6).

Its debug print also shows undetectable and callable on the object's map.

The real document.all (browser only)JavaScript
// This only works in a browser. Node has no document.all.console.log(typeof document.all);console.log(document.all == null);console.log(document.all == undefined);console.log(document.all === null);console.log(Boolean(document.all));
Where the rules come from

Annex B is the part of the JavaScript spec for old browser features. It defines a hidden flag called the [[IsHTMLDDA]] internal slot. Because document.all has it, document.all is falsy, typeof gives "undefined", and document.all == null is true. But document.all === null is still false.

Four values: null, undefined, document.all, and 0. x === null is true only for null. x == null is true for null, undefined, and document.all, and false for 0.valuex === nullTestNullx == nullTestUndetectablenulltruetrueundefinedfalsetruedocument.allfalsetrue0falsefalse
x === null matches only null. x == null also matches undefined and the old browser value document.all, but never 0.

Objects whose prototype is null

Most objects have a prototype: another object they borrow properties from. A plain {} borrows from Object.prototype, which gives it names such as toString.

Object.create(null) and the literal { __proto__: null } make real objects whose prototype is null. They borrow nothing. That makes them safe dictionaries: objects you use to look up values by string keys. A key such as "toString" can't find a borrowed method by accident. But these objects are still objects. They are not the value null.

An object with no prototypePop out in the code editor (opens in a new tab)JavaScript
const dict = Object.create(null);dict.answer = 42;console.log(Object.getPrototypeOf(dict) === null);console.log("toString" in dict);console.log(dict.answer); const literal = { __proto__: null, answer: 42 };console.log(Object.getPrototypeOf(literal) === null);
  • Line 1 makes dict with Object.create(null).
  • Line 3 prints true: the prototype of dict is null.
  • Line 4 prints false: dict has no toString, because it borrows nothing.
  • Line 5 prints 42, the value saved on line 2.
  • Lines 7 and 8 do the same with a literal and print true.
How V8 stores these objects (Node only)JavaScript
const created = Object.create(null);const literal = { __proto__: null };const ordinary = {};console.log(%HasFastProperties(created));console.log(%HasFastProperties(literal));console.log(%HasFastProperties(ordinary));%DebugPrint(created);

V8 can store the properties of an object in two ways:

  • Fast properties sit in fixed places that the object's map describes.
  • Dictionary mode, also called slow properties, keeps them in a lookup table, like a small phone book.

%HasFastProperties is a special V8 helper. It gives true when an object uses fast properties. The lesson's test shows:

  • %HasFastProperties(Object.create(null)) is false.
  • %HasFastProperties({ __proto__: null }) is false.
  • %HasFastProperties({}) is true.

V8's bootstrapper is the code that sets up the built-in objects. Its comment says "Set up slow map for Object.create(null) instances without in-object properties". It saves that map as slow_object_with_null_prototype_map.

When to use one

A null-prototype object is a good choice when you want no borrowed names. It also blocks some prototype pollution bugs, where an attacker adds properties to Object.prototype. A JavaScript Map is often clearer when you add and delete keys often, need keys that aren't strings, or want the keys in the order you added them. See Prototype pollution for the security side.

A property lookup stops at null

When you read a property, JavaScript looks in the object first. If the property isn't there, it goes to the object's prototype and looks there. Then it goes to that object's prototype, and so on. This line of prototypes is the prototype chain. Every chain ends at null.

The spec step OrdinaryGet says it plainly: when the next prototype is null, the read gives undefined.

V8 can also remember the answer to a lookup. Each map has a prototype_validity_cell: a marker that changes when the prototype chain changes. An inline cache is V8's saved answer for a property read at one place in your code. It can check that marker before it reuses its answer.

Read one property that exists and one that doesn'tPop out in the code editor (opens in a new tab)JavaScript
const base = { shared: "base value" };const middle = Object.create(base);const child = Object.create(middle); console.log(child.shared);console.log(child.missing);console.log(Object.getPrototypeOf(Object.prototype) === null);
  • Line 1 makes base, which owns shared.
  • Line 2 makes middle, with base as its prototype.
  • Line 3 makes child, with middle as its prototype.
  • Line 5 prints base value. child and middle don't own shared, but base does.
  • Line 6 prints undefined. No object owns missing, so the walk reaches null.
  • Line 7 prints true: the prototype of Object.prototype is null.
Step through a property lookup that reaches null
Step 0 of 10Ready
Your turn: follow the blue line

Step through two real property reads: one finds shared on base, and one walks all the way to null.

Running in
  1. script
Next: line 1
const middle = Object.create(base);const child = Object.create(middle); console.log(child.shared);console.log(child.missing);console.log(Object.getPrototypeOf(Object.prototype) === null);
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.
Reading child.missing. Arrows go from child to middle to base, down to Object.prototype, then to null. None of them has missing. At null the walk stops and the read gives undefined.child.missingchildmiddlebaseObject.prototypenullthe walk stopsundefinedthe result
Each arrow points to the next prototype. No object has missing, so the walk reaches null and stops. Then child.missing gives undefined.

Prototype internals explains maps and prototype pointers in more detail. Inline caches explains why V8 wants to save an answer like "not found, as long as this chain doesn't change".

Try the checks, then use them in real code

Pick a value below. The playground runs five checks on it: typeof, loose null (== null), strict null (=== null), Boolean(), and Number(). It also names the V8 check that decides the answer.

Playground: which null check decides?
The selected checkPop out in the code editor (opens in a new tab)JavaScript
const value = null;console.log(typeof value);console.log(value == null);console.log(value === null);console.log(Boolean(value));
What you getnull
typeofobject
== nulltrue
=== nulltrue
Booleanfalse
Number(...)0
Which check?

TestNull answers `=== null`. TestUndetectable answers `== null`.

Try it yourself

null has typeof object. With == null it gives true, with === null it gives true, and Boolean(...) gives false.

Each value is worked out by your browser's own JavaScript. The document.all row is a model, based on the spec and on a check run in Node.
Sort the facts about null
  • V8 saves "object" as the typeof answer of null.
  • null + 1 is 1, because null as a number is 0.
  • x == null becomes the instruction TestUndetectable.
  • document.all === null is false, but document.all == null is true.
  • Object.create(null) has no toString.
  • %HasFastProperties(Object.create(null)) is false in V8.
  • Object.getPrototypeOf(Object.prototype) === null.
  • Reading a property nobody owns gives undefined when the walk reaches null.
Try it yourself
0 of 8 correct

Put each card under the idea it belongs to.

Choose a category for every card. You can change an answer at any time; Reset clears them all.

In real code, you often want to treat null and undefined as "missing" but keep values such as 0 and "". Those two are falsy: they count as false in an if. But they are real values.

Treat only null and undefined as missingPop out in the code editor (opens in a new tab)JavaScript
function describeResult(value) {  if (value == null) {    return "missing";  }  return "present: " + value;} console.log(describeResult(null));console.log(describeResult(undefined));console.log(describeResult(0));console.log(describeResult(""));
  • Line 2 uses value == null, so both null and undefined give "missing".
  • Lines 8 and 9 print missing.
  • Line 10 prints present: 0, and line 11 prints present: with nothing after it. 0 and "" are kept.

Which check to use:

  • Use value === null when only null should match.
  • Use value == null when both null and undefined mean "missing".
  • Don't use typeof value === "object" alone. It is also true for null, so check value !== null too.
  • Use Object.create(null) for simple dictionaries. Use Map when you need more.

Common misconceptions

  • "typeof null proves that null is an object." No. The answer was kept from 1995 so old code keeps working. Reading a property of null still throws an error.
  • "== null checks for any falsy value." No. It matches only null, undefined, and values with the undetectable mark. It does not match 0, "", or false.
  • "document.all is just undefined." No. It is an old browser object with special rules. document.all === null and document.all === undefined are both false.
  • "An object with a null prototype is null." No. It is a real object. Only its prototype is null.
  • "Dictionary mode is always bad." No. V8 picks it on purpose for objects used as dictionaries, where keys come and go.

The next table compares four values. Each row is one value. Each column asks one question about it.

Values that are easy to mix up
ValueWhat typeof gives== null?=== null?As a numberAs a booleanHow V8 handles it
null"object"truetrue0falseThe one null oddball. Its map has the undetectable mark.
undefined"undefined"truefalseNaNfalseThe one undefined oddball. Its map has the undetectable mark.
document.all"undefined"truefalseNaNfalseThe spec gives it the [[IsHTMLDDA]] flag. V8 uses the undetectable mark.
Object.create(null)"object"falsefalseTypeErrortrueA real object with a null prototype, in dictionary mode. No undetectable mark.

This table takes ideas that share the word "null". Each row says what the idea really means, and what it is not.

Ideas that share the word null
IdeaWhat it really meansWhat it is not
typeof nullAn answer kept from 1995 that gives "object".A sign that null has properties or methods.
value == nullA check that matches null, undefined, and values with the undetectable mark.A check for any falsy value. It does not match 0, "", or false.
An object with a null prototypeA real object whose prototype is null.The value null, or a value that is == null.
Dictionary modeThe way V8 stores properties in a lookup table, used for objects with a null prototype.A JavaScript Map. Use Map for keys that aren't strings, or keys you add and delete often.
Real-life analogyThe end of a queue

You ask a question and pass it along a queue, from one person to the next. When the last person has no one to pass it to, the search stops.

In real life: Each person points to the next person
In JavaScript: Each object points to its prototype
In real life: The last person has no next person
In JavaScript: The last prototype in the chain is null
In real life: Someone standing alone has nobody to point to
In JavaScript: An object made with Object.create(null) borrows no names

Practice exercises

6 EXERCISES
Exercise 1 · Warm-upPredict what null becomes

Predict the three outputs, in order.

Starter codePop out in the code editor (opens in a new tab)JavaScript
console.log(null + 1);
console.log(null >= 0);
console.log(null == 0);

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · Warm-upLoose and strict checks of null and undefined

    What do the loose check and the strict check print?

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    console.log(null == undefined);
    console.log(null === undefined);

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticeRead an object with no prototype

      Predict both outputs.

      Starter codePop out in the code editor (opens in a new tab)JavaScript
      const dict = Object.create(null);
      console.log("toString" in dict);
      console.log(Object.getPrototypeOf(dict) === null);

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · PracticeUse the old tag model

        What does the model print for the all-zero word that stands for null?

        Starter codePop out in the code editor (opens in a new tab)JavaScript
        const JSVAL_OBJECT = 0;
        const nullWord = 0x00000000;
        console.log((nullWord & 0x7) === JSVAL_OBJECT ? "object" : "other");

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 5 · PracticePick the check that also catches document.all

          Which check matches null, undefined, and the old document.all?

          Answer, then press Check. Spacing and letter case don’t matter.

            Exercise 6 · ChallengeExplain a property that isn't there

            What value comes back for child.missing?

            Starter codePop out in the code editor (opens in a new tab)JavaScript
            const base = { answer: 42 };
            const child = Object.create(base);
            console.log(child.answer);
            console.log(child.missing);

            Answer, then press Check. Spacing and letter case don’t matter.

              Quiz: check your understanding

              8 QUESTIONS
              null internals quiz · 8 questionsScore: first tries count
              1. Question 1 of 8How does V8 store null?

                Choose an answer to see the explanation.

              2. Question 2 of 8What do these three lines print?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                console.log(null + 1);
                console.log(null >= 0);
                console.log(null == 0);

                Choose an answer to see the explanation.

              3. Question 3 of 8Why does typeof null give "object" today?

                Choose an answer to see the explanation.

              4. Question 4 of 8Which instruction did Node 22 make for x == null?

                Choose an answer to see the explanation.

              5. Question 5 of 8What does this code print about an object with no prototype?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                const dict = Object.create(null);
                console.log("toString" in dict);
                console.log(Object.getPrototypeOf(dict) === null);

                Choose an answer to see the explanation.

              6. Question 6 of 8Which results belong to document.all in a browser?

                Choose an answer to see the explanation.

              7. Question 7 of 8What does this lookup code print?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                const base = { answer: 42 };
                const child = Object.create(base);
                console.log(child.answer);
                console.log(child.missing);

                Choose an answer to see the explanation.

              8. Question 8 of 8When is Object.create(null) a good tool?

                Choose an answer to see the explanation.

              Key takeaways

              • V8 keeps one read-only null. It holds ready-made answers: 0 as a number, "null" as a string, and "object" for typeof.
              • typeof null gives "object" because, in 1995, null was an all-zero word, and 000 was the object tag.
              • === null becomes the instruction TestNull. == null becomes TestUndetectable.
              • document.all is an old browser object. It is falsy and == null, but it is not null.
              • An object made with Object.create(null) is a useful dictionary. Every property lookup stops when it reaches a null prototype.

              Remember the one-liner.
              null is one empty value made once, with special storage and fast checks, and it is also where every prototype chain ends.

              Up next: Strings inside the engine. You'll see the different ways V8 stores strings, such as one-byte strings, two-byte strings, ropes, and slices.

              CompleteFrontend Clear concepts. Working examples.