How Does the new Keyword Work in JavaScript?

Every time you type new Something(), JavaScript runs four precise steps behind the scenes. Understanding those steps explains constructor functions, prototype linking, and instance creation all at once.
What Is a Constructor Function?
Before new makes sense, you need to understand what it's working with. A constructor function is just a regular JavaScript function that's designed to be called with new. There's no special syntax — it's purely a convention. By convention, we capitalize the first letter.
// Regular function — returns undefined, no special behavior
function greet(name) {
console.log('Hello, ' + name);
}
// Constructor function — designed to be called with new
// Capitalized name signals: "use new with this"
function Person(name, age) {
this.name = name;
this.age = age;
}
// Calling it with new creates an instance
const alice = new Person('Alice', 28);
console.log(alice.name); // 'Alice'
console.log(alice.age); // 28
The this keyword inside the function is the key. When called normally, this refers to the global object (or undefined in strict mode). When called with new, this refers to the brand-new object being created. That's what new arranges.
Naming convention: Capitalize constructor functions:
Person,Car,BankAccount. Lowercase for regular functions. This signals to every reader — and to you at 2am — that this function expectsnew.
The Four Steps new Runs Under the Hood
When you call a constructor with new, four things happen in order:
| Step | What happens |
|---|---|
| ① Create empty object | A fresh {} is allocated in memory. Let's call it newObj. |
| ② Link prototype | newObj.[[Prototype]] is set to Constructor.prototype. The chain is born. |
③ Bind this |
The constructor runs with this pointing to newObj. Properties get attached. |
| ④ Return the object | newObj is returned automatically — unless the constructor returns a different object explicitly. |
Here's what JavaScript is doing internally — written as pseudocode so you can see the full sequence:
function simulateNew(Constructor, ...args) {
// Step 1 — create a blank object
const newObj = {};
// Step 2 — link [[Prototype]] to Constructor.prototype
Object.setPrototypeOf(newObj, Constructor.prototype);
// Step 3 — run the constructor with this = newObj
const returned = Constructor.call(newObj, ...args);
// Step 4 — return newObj, unless constructor returned an object
return (returned !== null && typeof returned === 'object')
? returned
: newObj;
}
// These two lines are now equivalent:
const alice = new Person('Alice', 28);
const alice2 = simulateNew(Person, 'Alice', 28);
🔬 Original Insight: The four-step sequence reveals something subtle: methods aren't copied to instances. When you write
Person.prototype.greet = function() {...}, every instance accesses the same function object through the prototype chain. Creating 1,000 Person instances only creates onegreetfunction. This is why constructors beat factory functions for memory efficiency at scale.
How Does Prototype Linking Actually Work?
When a function is called with new, the constructor's prototype property becomes the resulting object's [[Prototype]]. You can verify this:
Object.getPrototypeOf(alice) === Person.prototype // true
There are two distinct "prototype" concepts that trip people up — don't confuse them:
function Person(name) {
this.name = name;
}
// 1. Constructor.prototype — a plain object, visible, configurable
// Methods you add here are shared by ALL instances
Person.prototype.greet = function() {
console.log('Hi, I am ' + this.name);
};
const alice = new Person('Alice');
// 2. instance.[[Prototype]] — hidden, set by new, points to Person.prototype
// Accessed via Object.getPrototypeOf() or the older __proto__
console.log(Object.getPrototypeOf(alice) === Person.prototype); // true
console.log(alice.__proto__ === Person.prototype); // true (legacy)
// alice can call greet() even though it's NOT on alice itself
alice.greet(); // 'Hi, I am Alice'
// Proof: greet lives on the prototype, not the instance
console.log(alice.hasOwnProperty('name')); // true — own property
console.log(alice.hasOwnProperty('greet')); // false — from prototype
Prototype chain lookup: When you access alice.greet(), JavaScript first checks alice itself — not found. Then it walks up to alice.[[Prototype]] (which is Person.prototype) — found. If it still wasn't there, it'd continue to Object.prototype. If not found there either: undefined. This is the prototype chain.
alice
└── [[Prototype]] → Person.prototype
└── greet: ƒ()
└── [[Prototype]] → Object.prototype
└── hasOwnProperty: ƒ()
└── [[Prototype]] → null
Building Instances: A Complete Working Example
// ── Define the constructor ──────────────────────────────
function BankAccount(owner, balance) {
this.owner = owner; // own property on each instance
this.balance = balance; // own property on each instance
}
// ── Add shared methods to the prototype ────────────────
BankAccount.prototype.deposit = function(amount) {
this.balance += amount;
console.log(`\({this.owner} deposited £\){amount}`);
};
BankAccount.prototype.withdraw = function(amount) {
if (amount > this.balance) {
console.log('Insufficient funds');
return;
}
this.balance -= amount;
};
BankAccount.prototype.summary = function() {
return `\({this.owner}: £\){this.balance}`;
};
// ── Create instances ───────────────────────────────────
const alice = new BankAccount('Alice', 1000);
const bob = new BankAccount('Bob', 500);
// ── Each instance has its own data ─────────────────────
alice.deposit(200); // 'Alice deposited £200'
bob.withdraw(100); // bob.balance = 400
console.log(alice.summary()); // 'Alice: £1200'
console.log(bob.summary()); // 'Bob: £400'
// ── Verify prototype relationships ─────────────────────
console.log(alice instanceof BankAccount); // true
console.log(Object.getPrototypeOf(alice) === BankAccount.prototype); // true
// deposit lives on the prototype, NOT on alice
console.log(alice.hasOwnProperty('deposit')); // false
console.log(alice.hasOwnProperty('owner')); // true
// Both instances share the SAME deposit function object
console.log(alice.deposit === bob.deposit); // true ← memory efficient!
That last line is worth pausing on. alice.deposit === bob.deposit is true because they're literally the same function object — not a copy. Create a million accounts and you still only have one deposit function.
What Happens If You Forget new?
This is the most common gotcha with constructors. Calling a constructor without new doesn't throw an error by default — it does the wrong thing silently.
function Person(name) {
this.name = name;
}
// Correct — this = new empty object
const alice = new Person('Alice');
console.log(alice.name); // 'Alice'
// Oops — called without new
// In non-strict mode: this = window (global object)
// In strict mode: this = undefined → TypeError crash
const bob = Person('Bob');
console.log(bob); // undefined — function returned nothing
console.log(window.name); // 'Bob' — polluted the global scope!
// ── Guarded constructor (new.target) ──────────────────
function SafePerson(name) {
if (!new.target) {
throw new Error('SafePerson must be called with new');
}
this.name = name;
}
SafePerson('Bob'); // Throws immediately — clear error message
Use strict mode. Add
'use strict';at the top of your files. In strict mode,thisinside a function called withoutnewisundefinedinstead ofwindow— you get a crash immediately rather than silent global pollution.
Constructor Functions vs. ES6 Classes
ES6 classes use the same four-step new mechanism internally — a class is syntactic sugar over constructor functions.
// ── Constructor function style (ES5) ───────────────────
function PersonOld(name, age) {
this.name = name;
this.age = age;
}
PersonOld.prototype.greet = function() {
return `Hi, I'm ${this.name}`;
};
// ── Class style (ES6) — same result, nicer syntax ──────
class PersonNew {
constructor(name, age) {
this.name = name;
this.age = age;
}
// Methods defined in the class body go to PersonNew.prototype
greet() {
return `Hi, I'm ${this.name}`;
}
}
// Under the hood: identical prototype structure
const p1 = new PersonOld('Alice', 28);
const p2 = new PersonNew('Alice', 28);
console.log(Object.getPrototypeOf(p1) === PersonOld.prototype); // true
console.log(Object.getPrototypeOf(p2) === PersonNew.prototype); // true
| Feature | Constructor Function | ES6 Class |
|---|---|---|
| Syntax | Plain function, capitalize by convention | class keyword, cleaner |
| Prototype linking | Identical | Identical |
| Hoisting | Fully hoisted | Not hoisted |
| Strict mode | Optional | Always strict |
Calling without new |
Silent bug (non-strict) | Always throws TypeError |
| Static methods | Manual: Fn.method = ... |
static keyword built-in |
🔬 Unique Insight: Classes enforce
newby throwing aTypeErrorwhen called without it — the protection you had to manually implement withnew.targetin constructor functions. This is the most practical safety advantage of class syntax, not just the tidier syntax.
Frequently Asked Questions
What does instanceof actually check?
alice instanceof Person walks up alice's prototype chain and checks whether Person.prototype appears anywhere in it. It doesn't check the constructor directly — it checks the prototype chain. If you reassign Person.prototype after creating instances, instanceof can give surprising results on old instances.
Should I put methods inside the constructor or on the prototype?
Always put methods on the prototype (or use class syntax, which does this automatically). Methods defined inside the constructor body with this.greet = function(){} are copied onto every instance — one copy per object, every time. At 10,000 instances, that's 10,000 function objects in memory instead of one shared one. Put methods on the prototype; put data on this.
What if my constructor returns an object explicitly?
If the constructor function returns a non-primitive value (an object or array), that return value becomes the result of the new expression and the newly created newObj is discarded. If it returns a primitive or nothing, the return is ignored and newObj is returned as normal.
What's the difference between __proto__ and prototype?
__proto__ (or Object.getPrototypeOf()) is the internal prototype link on each object instance — the hidden pointer new sets in step 2. Constructor.prototype is the plain object that all instances will point to. One is on every instance; the other is on the constructor function. Use Object.getPrototypeOf() in real code — __proto__ is a legacy accessor.
How does new relate to ES6 classes?
Classes are a type of function — the only salient difference is that the prototype property of a class is not writable, and classes always run in strict mode. The new keyword runs the same four steps whether you write class Person or function Person. Understanding constructor functions means you understand classes at the engine level.
Wrapping Up
The new keyword does exactly four things, in exactly this order: creates an empty object, links its [[Prototype]] to the constructor's .prototype, binds this so the constructor body can attach properties, and returns the object automatically.
Constructor functions are just regular functions — the capitalization is a convention, not a rule. Methods belong on the prototype (shared, memory-efficient). Data belongs on this (unique per instance). Forgetting new gives you a silent, global-polluting bug — add new.target guards or switch to classes, which throw on unguarded calls.
ES6 classes didn't change any of this. They cleaned up the syntax and added strict mode enforcement, but the engine still runs these same four steps every time you write new. Master the constructor model once and you'll read both styles with equal confidence.
Key Takeaways
The
newkeyword runs exactly four steps: creates an empty object, links its prototype, bindsthis, then returns the object automatically.A constructor function is just a regular function — the convention is to capitalize its name so you know to call it with
new.Prototype linking is the key step: every instance gets a hidden
[[Prototype]]pointer to the constructor's.prototypeobject.Methods on
Constructor.prototypeare shared across all instances — only data properties live on each instance individually.Forgetting
newis a silent bug:thispoints to the global object instead of a new instance.



