JavaScript Object Internals | From Hidden Classes to Hash Maps

7 min
JavascriptData StructuresMemory

Ever wondered what really happens when you write below code in Javascript?

const obj = { a: "Hey", b: 123 };

Have you ever considered,

  • Whether those keys will be stored on the memory along with their values.
  • Whether this is an hash map-like structure?
  • Are those values stored in the memory contiguous?

There are lots of questions coming to my mind whenever I stop writing the code and then start thinking how engine can understand what to do, and how those things are stored in the memory :)

I am kind of an overthinking person, so as you can see, it blocks my working routine and push me to start digging into those unknowns instead of building a product hahaha

Anyway, thanks to that, I am learning lots of things whether I need those information or not.

Let's first start with by understanding of the data types in Javascript

Primitives and Objects

Normally, we can store our primitive variables (integers, chars, boolean, etc.) by their values or by their references such as objects, arrays, functions, etc.

It is simpler to understand how things are stored by values for example

const a = 123;

When JS Engine sees above code, the flow like that:

  1. It parses the code into Abstract Syntax Tree (AST)
  2. It creates a binding in the nearest lexical environment

So internally

LexicalEnvironment: {
  "a" → binding pointing to value `123`
}

This is not like compiling to a raw memory address (as in C/C++/Rust).

Instead:

  • "a" is entered into a scope map (symbol table)
  • That scope map lives in a closure environment record

Where does that value (123) be allocated in the memory?

It stored on the stack or in register, depending on optimizations. The binding for "a" is a reference to that memory location (abstracted by the engine).

Later, when you write console.log(a)

The engine:

  1. Looks up a in the lexical environment chain
  2. Finds the binding ➡️ fetches its value ➡️ returns 123

So a is not compiled to a memory address directly like in C, C++ or Rust.

Because Javascript is:

  • Dynamic
  • Lexically Scoped
  • Not statically typed

If V8 (JS Engine) detects that a is a 🔥 hot variable, its optimizing compiler (TurboFan) may "inline the value", and "replace scope lookups with direct memory offsets" (Don't want to dive deep into how JS Engine and Ignition works in that blog).

If we have an idea how primitives are handled by JS Engine and stored, let's dive into how objects are handled.

Are JavaScript Objects Hash Maps?

Most of the people say that Javascript Objects are Hash Maps, but this is not 100% true. JS engines may use hash maps depending on the object's usage patterns. In V8, Dictionary Mode explicitly uses a hash map-like structure for dynamic cases.

Actually, we can say there are two types of modes affecting data structure of JS Objects:

1. Dictionary Mode (Hash map-like structure):

Triggered when:

  • You delete a property
  • You use computed keys obj[Math.random()]
  • You dynamically add keys in unpredictable ways
const obj = {
  a: 1,
  b: 2,
  c: 3,
};
 
// 1. Deleting a property forces dictionary mode
delete obj.b;
 
// 2. Adding a property with a computed (non-static) key forces dictionary mode
const dynamicKey = Math.random().toString();
obj[dynamicKey] = 42;
 
// 3. Adding keys out of order / dynamically forces dictionary mode
obj["z"] = 99;
obj["y"] = 88;

2. Fast Mode (HiddenClass + Slots):

  • You create an object with fixed properties
  • You add properties in a consistent order
  • You don't delete properties
const obj = { x: "hey", y: 123 }; // Fast Mode

Do we store keys of the Object in Memory?

Yes, we do! BUT, the Dictionary mode and Fast Mode approach that storing keys in differently.

Keys are interned strings (shared memory for repeated strings) and stored either in the descriptor array (Fast Mode) or as part of dictionary entries (Dictionary Mode).

Terms like Hidden Class, Map, and DescriptorArray are specific to Google's V8 JavaScript engine (used in Chrome and Node.js). Other engines like Firefox's SpiderMonkey or Safari's JavaScriptCore implement similar concepts under different names and strategies.

In Dictionary Mode

Dictionary Mode

Each field of the object stored in hash map-like structure. We call that structure as NameDictionary.

How engines behave when they encounter with the code line (obj) like above:

1. Keys are hashed using hash function

hashFunction("x"); // This results a hashed integer like 1263128973
hashFunction("y");

2. Computing the index

The key is hashed, and the result is used (often via bitmasking or modulo) to compute the index into an internal array where the {key, value} pair is stored. If that index is occupied, probing is used to find the next available slot.

hashedInteger % table_size = index;

3. Dictionary Entry allocation

We call {key, value} as Dictionary Entry in C++, and V8 allocates that in heap like below

struct DictionaryEntry {
  Name* key;              // Interned String
  Object* value;          // JS value
  PropertyDetails flags;
}

Whenever we access obj.a, the V8 engine performs a lookup for the "a" key in the internal hash table.

⚠️ The main drawback of Dictionary Mode

Objects do not share structure. For example, if you create a thousand objects with the same shape, the engine creates a thousand independent internal dictionaries — each storing its own keys, even if they are identical.

This is inefficient compared to Fast Mode, where objects with the same structure can share a single HiddenClass and Descriptor Array, reducing memory usage and improving access speed.

In Fast Mode

We do store keys just once in a Hidden Class (Which is also called as Map in engine context)

A HiddenClass is like a runtime-defined struct layout, and it can be shared among thousands of objects with the same structure!

const obj = { x: 1, y: 2 };
const obj2 = { x: 3, y: 4 };

Both obj and obj2 point to the same Map.

  • No duplicate keys are stored
  • Engine uses pointer + offset arithmetic for access.
struct JSObject {
  Map* hidden_class;     // at offset 0 - pointer to the Map
  void* value_slot0;     // offset +8 - value for first prop
  void* value_slot1;     // offset +16 - value for second prop
}

As you can see, above struct holds the values, not the keys. The keys are in the Hidden Class.

Fast Mode Diagram

There is a Descriptor Array which is a metadata table attached to the HiddenClass (Map) that holds each property name + slot/offset + attributes.

You can think HiddenClass and Descriptor Array like below, behind the scenes V8 engines uses C++ structures to handle with those things

HiddenClass1 = {
  descriptor_array: DescriptorArray1,
  in_object_slots: 2,
  prototype: Object.prototype,
};
DescriptorArray1 = [
  {
    name: "x",
    slot_index: 0,
    attributes: { writable: true, enumerable: true },
  },
  {
    name: "y",
    slot_index: 1,
    attributes: { writable: true, enumerable: true },
  },
];

So, when you want to access obj.x:

The JS engine looks up the property in the Descriptor Array to find the slot index for "x". Based on this slot, it calculates the offset in the JSObject to locate the memory address and retrieve the value.

You can see the flow of the property access in Fast Mode

Fast Mode Flow

Fast Mode vs Dictionary Mode: Performance Implications

Dictionary Mode prevents optimizations like inline property access, so it's best avoided when performance matters.

Metaphor: The Fast Lane vs. Dirt Road

Imagine a sports car driving on a well-paved race track— smooth, fast, and predictable. This is Fast Mode.

Now imagine that same car trying to speed through a twisting dirt path full of potholes and surprises. That's Dictionary Mode.

Fast Mode:

Uses a HiddenClass (aka Map) and a Descriptor Array.

Engine knows:

  • Property x is at slot 0 → offset +8 (Each slot is 8 bytes in Javascript)
  • Property y is at slot 1 → offset +16

Access becomes a direct pointer + offset calculation — like C structs.

Dictionary Mode:

Each property access becomes:

  1. Compute hash of key
  2. Probe hash table
  3. Check key equality
  4. Retrieve value

No reuse of structure layouts = no inline cache optimization.

Conclusion

Understanding how JavaScript engines handle objects internally helps us write more performant code. By avoiding operations that trigger Dictionary Mode (like deleting properties or using computed keys unpredictably), we can keep our objects in Fast Mode and benefit from optimizations like shared Hidden Classes and direct memory access.

The key takeaway: JavaScript objects aren't simply hash maps. They're sophisticated data structures that adapt based on usage patterns, balancing flexibility with performance.