How Browsers Store and Manage Variables in Memory

Browser Edition

February 24, 2026

Changed the order of object properties in my code. Same data, just rearranged.

Performance got worse.

What?

Turns out JavaScript engines care about object shape. Not just what properties you have, but what order you create them in.

The Thing Nobody Tells You

JavaScript feels dynamic. Create objects however you want. Add properties whenever. Delete them if you want. Total freedom.

But underneath, V8 (Chrome’s JavaScript engine) is desperately trying to make your dynamic code run like static, typed code.

And it’s really good at it. When you cooperate.

What I Thought Was Happening

Thought objects were just hash maps. Key-value pairs. Access any property, engine looks it up, done.

const user = {\n  name: "Alice",\n  age: 30\n};\n\nconsole.log(user.name); // Engine looks up "name", finds "Alice"

Simple hash table lookup, right?

That’s what’s happening... sort of. But V8 is doing way more optimization behind the scenes.

Hidden Classes (The Trick V8 Uses)

V8 doesn’t actually store objects as hash maps. It creates hidden classes (also called shapes or maps).

When you create an object:

const user = {\n  name: "Alice",\n  age: 30\n};

V8 creates a hidden class that describes this object’s structure:

  • Property “name” at offset 0

  • Property “age” at offset 4

Now when you access user.name, V8 doesn’t do a hash lookup. It knows “name is at offset 0” from the hidden class. Direct memory access. Way faster.

Objects With Same Properties Share Hidden Classes

Here’s where it gets interesting:

const user1 = {\n  name: "Alice",\n  age: 30\n};\n\nconst user2 = {\n  name: "Bob",\n  age: 25\n};

Both objects share the same hidden class. V8 sees the pattern. Same properties, same order. One hidden class, reused for both.

Fast property access for both objects.

Change the Order, Break the Optimization

Now do this:

const user1 = {\n  name: "Alice",\n  age: 30\n};\n\nconst user2 = {\n  age: 25,      // Age first\n  name: "Bob"   // Name second\n};

Different property order. V8 creates two hidden classes. One for each pattern.

Any function that takes users now has to handle two different shapes. Can’t optimize as well.

When I Actually Hit This

Built a data processing function. Looped through thousands of objects. Each iteration accessed the same properties.

function processUsers(users) {\n  let total = 0;\n  for (const user of users) {\n    total += user.age;\n  }\n  return total;\n}

Worked fine. Then someone started creating user objects in different ways throughout the codebase:

// Sometimes like this\nconst user1 = { name: "Alice", age: 30, email: "alice@example.com" };\n\n// Sometimes like this\nconst user2 = { age: 25, name: "Bob", email: "bob@example.com" };\n\n// Sometimes like this\nconst user3 = { email: "charlie@example.com", name: "Charlie", age: 35 };

Function slowed down. V8 couldn’t assume a consistent shape anymore. Had to check which hidden class each object used.

Fixed it by using a constructor function:

function createUser(name, age, email) {\n  return { name, age, email }; // Always same order\n}

Now all user objects had the same hidden class. Loop got fast again.

Adding Properties Later Breaks Things Too

This kills optimization:

const user = { name: "Alice" };\nuser.age = 30;        // Added later\nuser.email = "...";   // Added even later

V8 creates a new hidden class every time you add a property. user goes through three different hidden classes.

Better:

const user = {\n  name: "Alice",\n  age: 30,\n  email: "..."\n};

One hidden class. V8 happy.

Deleting Properties Is Even Worse

const user = { name: "Alice", age: 30, email: "..." };\ndelete user.email;

V8 can’t use the optimized hidden class anymore. Falls back to slower dictionary mode.

Instead of deleting, set to null or undefined:

user.email = null;

Object keeps its shape. V8 stays optimized.

Inline Caching (Why Shape Matters Even More)

V8 does something called inline caching. When it sees property access:

function getAge(user) {\n  return user.age;\n}

First time it runs, V8 records: “objects passed here have hidden class X, age is at offset 4.”

Next time, it checks: “is this object using hidden class X?” If yes, it jumps straight to offset 4. No lookup needed.

This is monomorphic inline caching. One shape, blazing fast.

When Multiple Shapes Show Up

If getAge gets called with different object shapes:

const user1 = { name: "Alice", age: 30 };\nconst user2 = { age: 25, name: "Bob" }; // Different order\n\ngetAge(user1); // Hidden class A\ngetAge(user2); // Hidden class B

V8 now has to cache both shapes. Polymorphic caching. Slower, but still optimized for these specific shapes.

More than ~4 different shapes? V8 gives up. Falls back to megamorphic mode. No caching. Slow.

The Array Example

Arrays have hidden classes too.

const arr1 = [1, 2, 3];\nconst arr2 = [1, 2, 3];

Both use the same hidden class for arrays of numbers.

But:

const arr1 = [1, 2, 3];\nconst arr2 = [1, 2, "three"]; // Mixed types

Different hidden classes. Arrays with consistent types are faster.

V8 even tracks element kinds:

  • PACKED_SMI_ELEMENTS (small integers)

  • PACKED_DOUBLE_ELEMENTS (numbers)

  • PACKED_ELEMENTS (anything)

More specific = faster.

Holes in Arrays Kill Performance

const arr = [1, 2, 3];\narr[5] = 6; // Creates holes at indices 3 and 4

V8 switches to HOLEY_ELEMENTS mode. Slower because it has to check if each index exists.

Better to keep arrays dense:

const arr = [1, 2, 3, undefined, undefined, 6];

No holes. Array stays packed. Faster iteration.

Why Constructor Functions Help

function User(name, age) {\n  this.name = name;\n  this.age = age;\n}\n\nconst user1 = new User("Alice", 30);\nconst user2 = new User("Bob", 25);

Guaranteed same property order. Same hidden class. V8 loves this.

Classes do the same thing:

class User {\n  constructor(name, age) {\n    this.name = name;\n    this.age = age;\n  }\n}

Consistent object shape across all instances.

The Object.assign Gotcha

const user = Object.assign({}, defaults, overrides);

If defaults and overrides have different properties, the result object’s shape is unpredictable. Different calls create different shapes.

Better to have a consistent structure:

const user = {\n  name: defaults.name ?? overrides.name ?? "",\n  age: defaults.age ?? overrides.age ?? 0,\n  email: defaults.email ?? overrides.email ?? ""\n};

Always the same properties. Same hidden class.

When I Measured This

Built a benchmark. Processed 100,000 objects two ways:

Version 1: Inconsistent shapes

const objects = [\n  { a: 1, b: 2 },\n  { b: 2, a: 1 },\n  { a: 1, b: 2, c: 3 },\n  // Mixed patterns\n];

Version 2: Consistent shapes

const objects = [\n  { a: 1, b: 2, c: null },\n  { a: 1, b: 2, c: null },\n  { a: 1, b: 2, c: 3 },\n  // Same structure\n];

Looped through, accessed properties.

Version 2 was ~40% faster. Just by keeping shapes consistent.

The TypeScript Connection

TypeScript helps with this indirectly.

interface User {\n  name: string;\n  age: number;\n  email: string;\n}

Forces you to create objects with consistent structure. Can’t accidentally reorder properties or add random ones.

Not why TypeScript exists, but it’s a nice side effect.

What V8 Can’t Optimize

Dynamic property access:

const key = Math.random() > 0.5 ? "name" : "age";\nconst value = user[key];

V8 can’t predict which property. No inline caching.

Computed property names:

const obj = {\n  [someVariable]: value\n};

Shape depends on runtime value. Hard to optimize.

Frequent shape changes:

for (let i = 0; i < 100; i++) {\n  obj[`prop${i}`] = i;\n}

Creates 100 different hidden classes. V8 gives up.

Does This Actually Matter?

For most code? No. V8 is fast enough that you won’t notice.

But for hot paths - code that runs thousands of times:

  • Tight loops

  • Event handlers firing constantly

  • Data processing functions

  • Animation loops

Keeping object shapes consistent makes a real difference.

What I Actually Do

  • Use constructors or classes for repeated objects

    Creates consistent shapes automatically.

  • Initialize all properties upfront

    Even if some are null or undefined.

  • Don’t delete properties

    Set to null instead.

  • Keep arrays homogeneous

    Same type for all elements when possible.

  • Avoid adding properties after creation

    Plan the shape ahead of time.

The Profiler Shows This

Chrome DevTools Performance tab shows function optimization status.

Record a profile. Look for warnings like:

  • “Not optimized: Polymorphic property access”

  • “Not optimized: Too many different shapes”

Those tell you where shape inconsistency is hurting performance.

The Mental Model

Think of objects as having a blueprint (hidden class).

Same blueprint = fast property access.

Different blueprints = engine has to check which blueprint, slower.

Too many blueprints = engine gives up, much slower.

When to Not Care

Prototyping? Don’t worry about this.

Configuration objects? Don’t worry about this.

Objects created once? Don’t worry about this.

Only matters in hot code paths with lots of objects or iterations.

The Honest Part

Spent years writing JavaScript without knowing this. Code worked fine.

Then hit a performance bottleneck in a data processing script. Profiled it. Saw polymorphic property access warnings everywhere.

Fixed the object shapes. Got a 2x speedup. No algorithm changes. Just consistent object structure.

Now I think about this when writing hot path code. Not everywhere. Just where performance matters.

The Quick Rules

Fast:

  • Same properties in same order

  • Initialize all properties upfront

  • Use constructors or classes

  • Keep arrays homogeneous

Slow:

  • Random property order

  • Adding properties later

  • Deleting properties

  • Mixed array types

  • Lots of different object shapes

Follow the fast rules in hot paths. Ignore them everywhere else.

What Actually Matters

V8 optimizes for patterns. Consistent patterns = fast code.

Not because V8 is dumb. Because it’s really smart and trying to optimize for static-like behavior in a dynamic language.

Help V8 help you. Keep object shapes predictable.

Your code gets faster without changing algorithms. Just by working with the engine instead of against it.


Thanks for reading Under The Hood! Subscribe for free to receive new posts and support my work.