How JavaScript Objects Actually Find Their Properties
An exploration of how JavaScript objects find properties through the prototype chain, and why reading, writing, and deleting properties behave differently.
JavaScript objects look simple on the surface. You store values inside them, and you access those values using dot notation.
const user = {
name: "Rishi",
age: 24
}
user.nameIt feels straightforward: the object contains the data, and JavaScript retrieves it.
But that simple mental model starts to break once you encounter prototypes. Suddenly an object can access properties that don’t actually exist inside it.
Understanding how that works requires looking at how JavaScript really finds properties.
Objects Don’t Always Store Everything Themselves
Consider this example:
const animal = { type: "animal" }
const dog = { name: "Buddy" }
Object.setPrototypeOf(dog, animal)Now if we ask for:
dog.typeJavaScript returns:
"animal"But if you inspect dog, it clearly does not contain a type property.
Instead, the property exists on animal.
This happens because JavaScript doesn’t only look inside the object itself when accessing properties. It follows a process known as prototype lookup.
The Prototype Chain
Every JavaScript object has an internal reference called [[Prototype]].
This reference points to another object. When a property is requested, JavaScript searches along this chain until it either finds the property or reaches the end.
The lookup process looks like this:
1. Check the object itself
2. If not found → check its prototype
3. If not found → check the prototype’s prototype
4. Continue until nullUsing the previous example, the structure looks like this:
dog
name: "Buddy"
↓
prototype
animal
type: "animal"
↓
prototype
Object.prototype
↓
nullSo when we run:
dog.typeJavaScript checks dog first. It doesn’t find type, so it moves to the prototype (animal) and finds it there.
Reading vs Writing Properties
One of the most important things to understand about this system is that reading and writing properties behave differently.
Reading a property searches through the prototype chain.
Writing a property does not.
Suppose we run:
dog.type = "dog"JavaScript does not modify the animal object. Instead, it creates a new property directly on dog.
Now the structure becomes:
dog
name: "Buddy"
type: "dog"
↓
prototype
animal
type: "animal"When JavaScript looks for dog.type, it finds the property immediately on dog, so the prototype is never consulted.
Shadowing Prototype Properties
This behavior is known as property shadowing.
The property defined on the object itself shadows the one defined in the prototype.
Both values continue to exist:
dog.type // "dog"
animal.type // "animal"But the prototype value becomes invisible when accessing the property through dog.
Deleting Properties Only Affects the Object
Another interesting behavior appears when deleting properties.
Consider this code:
delete dog.typeIf dog.type exists, it will be removed.
But if dog never had that property in the first place, the deletion does nothing. JavaScript does not attempt to remove properties from the prototype.
After deleting the shadowed property, the lookup simply falls back to the prototype again.
dog.type // "animal"Why JavaScript Works This Way
This design prevents objects from accidentally modifying shared behavior.
If writing to a property modified the prototype, a single object could change behavior for every object sharing that prototype.
Instead, JavaScript follows a predictable rule:
Reading searches the prototype chain.
Writing creates or updates the property on the object itself.
Deleting only affects the object where the property lives.
A System Built on Delegation
Because of this mechanism, JavaScript objects don’t really “inherit” properties in the traditional sense.
Instead, they delegate property access to their prototype when necessary.
The prototype becomes a fallback source of behavior.
This is how built-in objects work as well. For example, arrays appear to have methods like map, filter, and push.
But those methods aren’t stored in every array instance. They live on Array.prototype, and each array delegates to that shared object.
Once you see objects as part of a lookup chain, many JavaScript behaviors that initially seem strange start to feel much more logical.