The new Keyword in JavaScript: Constructors, Prototypes, and How Objects Are Born

Every JavaScript developer uses objects constantly. But not everyone thinks carefully about where objects come from — how they get created, what links them to shared behaviour, and what the new keyword is actually doing behind the scenes.
Understanding new is understanding one of the core mechanisms of JavaScript itself. This article walks through it completely: what new does step by step, how constructor functions work, how instances relate to their constructor, and how prototypes are wired up in the process.
Starting Point: Creating Objects Without new
Before looking at new, it helps to see the limitation it solves.
The simplest way to create an object in JavaScript is an object literal:
const user = {
name: "Priya",
age: 28,
greet() {
console.log(`Hi, I'm ${this.name}`);
}
};
user.greet(); // Hi, I'm Priya
This works perfectly for one object. But what if you need to create fifty users, all with the same shape — name, age, and a greet method? You could copy and paste the literal fifty times, changing the values each time. That is obviously not a solution.
A factory function helps:
function createUser(name, age) {
return {
name,
age,
greet() {
console.log(`Hi, I'm ${this.name}`);
}
};
}
const user1 = createUser("Priya", 28);
const user2 = createUser("Rahul", 34);
Better. But there is a problem: every object created by createUser gets its own separate copy of the greet function. Fifty users means fifty identical greet functions in memory. For a method that never changes, this is wasteful. It also means user1.greet !== user2.greet — they are different function objects even though they do the same thing.
The new keyword, combined with constructor functions and prototype linking, solves this. All instances share one copy of shared methods.
Constructor Functions
A constructor function is an ordinary JavaScript function written with the convention that it will be called with new. By convention, constructor function names start with a capital letter — this is not enforced by the language, but it signals to other developers (and your future self) that this function is meant to be used with new.
function User(name, age) {
this.name = name;
this.age = age;
}
Notice there is no return statement. There is no object literal being built up. Just assignments to this. On its own, calling this function without new would either do nothing useful or cause an error — this would not be what you expect.
Called with new, everything changes.
const user1 = new User("Priya", 28);
const user2 = new User("Rahul", 34);
console.log(user1.name); // Priya
console.log(user2.age); // 34
user1 and user2 are independent objects, each with their own name and age properties. The constructor function served as a template for both.
What new Does: Step by Step
When you write new User("Priya", 28), JavaScript performs four distinct steps. None of them are magic — understanding each one removes all the mystery from the keyword.
Step 1: A new empty object is created
JavaScript creates a brand new plain object. You can think of it as:
const obj = {};
This object exists in memory, but it has no properties yet and is not yet connected to anything.
Step 2: The object's prototype is linked
The new object's internal [[Prototype]] is set to User.prototype. Every function in JavaScript automatically has a prototype property — an object that serves as the shared prototype for all instances created by that constructor.
// This is what JavaScript does internally:
Object.setPrototypeOf(obj, User.prototype);
// Or equivalently:
// obj.__proto__ = User.prototype;
This is the step that makes shared methods work. More on this shortly.
Step 3: The constructor function runs with this set to the new object
The function body of User executes, but with this pointing to the newly created object from step 1.
// With this = obj, the constructor body runs:
obj.name = "Priya";
obj.age = 28;
Every assignment to this inside the constructor becomes a property on the new object.
Step 4: The new object is returned
If the constructor does not explicitly return an object, JavaScript automatically returns the new object created in step 1. The result is what gets assigned to user1.
If the constructor does return an object explicitly, that object is returned instead of the new one. If it returns a primitive (a number, string, etc.), the return value is ignored and the new object is returned anyway. This edge case almost never comes up in practice — constructors do not usually return anything.
Putting it together:
function User(name, age) {
// Step 1: new object created (internally)
// Step 2: this.__proto__ = User.prototype (internally)
// Step 3: constructor body runs with this = new object
this.name = name;
this.age = age;
// Step 4: this is returned automatically
}
const user1 = new User("Priya", 28);
Prototype Linking: Shared Methods
The reason new is worth understanding beyond just "it creates an object" is step 2 — the prototype link. This is what makes shared methods efficient.
You add shared methods to Constructor.prototype, and every instance automatically has access to them:
function User(name, age) {
this.name = name;
this.age = age;
}
User.prototype.greet = function() {
console.log(`Hi, I'm \({this.name} and I'm \){this.age} years old.`);
};
User.prototype.isAdult = function() {
return this.age >= 18;
};
const user1 = new User("Priya", 28);
const user2 = new User("Aryan", 16);
user1.greet(); // Hi, I'm Priya and I'm 28 years old.
user2.greet(); // Hi, I'm Aryan and I'm 16 years old.
user1.isAdult(); // true
user2.isAdult(); // false
greet and isAdult are defined once, on User.prototype. Both user1 and user2 access the same function objects. When you access user1.greet, JavaScript first looks for greet as an own property of user1, does not find it, then looks up the prototype chain and finds it on User.prototype.
You can verify this:
user1.hasOwnProperty("name"); // true — own property
user1.hasOwnProperty("greet"); // false — inherited from prototype
user1.greet === user2.greet; // true — same function object
This is the key difference from the factory function approach. With factory functions, each object had its own copy of greet. With the constructor + prototype approach, every instance shares one copy. At scale — hundreds of instances — the memory difference is significant.
The Relationship Between Constructor and Instance
Once an object is created with new, it carries a reference back to its constructor's prototype. You can inspect this:
console.log(Object.getPrototypeOf(user1) === User.prototype); // true
console.log(user1 instanceof User); // true
instanceof checks whether User.prototype appears anywhere in the prototype chain of user1. Because of step 2, it always does for objects created with new User(...).
The constructor function itself is also accessible:
console.log(user1.constructor === User); // true
This works because User.prototype has a constructor property pointing back to User, and user1 inherits it through the prototype chain.
Visualizing the links
user1
├── name: "Priya" (own property)
├── age: 28 (own property)
└── [[Prototype]] ──────► User.prototype
├── greet: function
├── isAdult: function
└── constructor ──► User
user1 owns name and age. It does not own greet or isAdult — it inherits them by following the [[Prototype]] link to User.prototype. User.prototype in turn has a constructor property that points back to the User function.
When JavaScript looks up a property:
Check the object itself — own properties first.
If not found, follow
[[Prototype]]to the next object in the chain.Keep following the chain until the property is found or the chain ends at
null.
This chain is the prototype chain. Every object participates in it.
Adding to the Prototype After Creation
Prototype methods can be added or changed at any time, and all existing instances immediately see the change:
function User(name, age) {
this.name = name;
this.age = age;
}
const user1 = new User("Priya", 28);
// Add a method after user1 already exists
User.prototype.introduce = function() {
console.log(`My name is ${this.name}.`);
};
user1.introduce(); // My name is Priya.
user1 was created before introduce was defined. It can still call it because introduce is on User.prototype, and user1's prototype link was set to User.prototype at creation time. Any change to that prototype object is reflected immediately in all instances.
This is powerful but requires care — modifying built-in prototypes like Array.prototype or String.prototype affects every array or string in your program and every library you use.
Instances Are Independent Objects
Each instance created with new has its own copy of the properties set inside the constructor. Changing one instance's properties does not affect another:
function User(name, age) {
this.name = name;
this.age = age;
}
User.prototype.greet = function() {
console.log(`Hi, I'm ${this.name}`);
};
const user1 = new User("Priya", 28);
const user2 = new User("Rahul", 34);
user1.name = "Meera"; // only changes user1
user1.greet(); // Hi, I'm Meera
user2.greet(); // Hi, I'm Rahul
user1.name and user2.name are completely separate properties on completely separate objects. The prototype link is shared; the instance data is not.
What Happens Without new
This is an important edge case. If you forget new and call a constructor as a regular function:
function User(name, age) {
this.name = name;
this.age = age;
}
const user = User("Priya", 28); // forgot new
console.log(user); // undefined
The function runs, but without new, this is not a new object — in non-strict mode, it is the global object (window in browsers). The properties get added to window.name and window.age instead. The function returns undefined (no explicit return), so user is undefined.
In strict mode ("use strict"), this is undefined inside functions that are not called as methods, so you get a TypeError immediately:
"use strict";
function User(name, age) {
this.name = name; // TypeError: Cannot set property 'name' of undefined
this.age = age;
}
const user = User("Priya", 28);
The strict mode error is the better outcome — you find out about the mistake immediately rather than debugging mysterious global variable pollution.
A guard against forgetting new
You can make a constructor safe to call either way:
function User(name, age) {
if (!(this instanceof User)) {
return new User(name, age);
}
this.name = name;
this.age = age;
}
const user1 = new User("Priya", 28); // works
const user2 = User("Rahul", 34); // also works — new is called internally
This is a pattern you see in older libraries that want to be forgiving about how they are called.
Constructor Functions vs ES6 Classes
ES6 introduced the class syntax, which many JavaScript developers use today for object creation. It is important to understand that classes do not introduce a new object model — they are syntactic sugar over constructor functions and prototypes.
// Constructor function style
function User(name, age) {
this.name = name;
this.age = age;
}
User.prototype.greet = function() {
console.log(`Hi, I'm ${this.name}`);
};
// Class style — identical behaviour, different syntax
class User {
constructor(name, age) {
this.name = name;
this.age = age;
}
greet() {
console.log(`Hi, I'm ${this.name}`);
}
}
Both versions create the same prototype chain. Both are used with new. Both produce instances where greet lives on the prototype. The class syntax is cleaner and harder to misuse — methods defined inside a class body are automatically non-enumerable and the class itself cannot be called without new, throwing a TypeError immediately if you forget. But underneath, it is the same mechanism.
Understanding constructor functions and new directly means you understand what classes are doing, which matters when you read older code, debug prototype chain issues, or encounter interview questions that probe below the syntax level.
Putting It All Together: A Complete Example
function Animal(name, sound) {
this.name = name;
this.sound = sound;
}
Animal.prototype.speak = function() {
console.log(`\({this.name} says \){this.sound}!`);
};
Animal.prototype.describe = function() {
console.log(`I am ${this.name}.`);
};
const dog = new Animal("Dog", "woof");
const cat = new Animal("Cat", "meow");
const cow = new Animal("Cow", "moo");
dog.speak(); // Dog says woof!
cat.speak(); // Cat says meow!
cow.speak(); // Cow says moo!
// All share the same speak function
console.log(dog.speak === cat.speak); // true
console.log(cat.speak === cow.speak); // true
// Own properties are independent
console.log(dog.hasOwnProperty("name")); // true
console.log(dog.hasOwnProperty("speak")); // false
// instanceof works correctly
console.log(dog instanceof Animal); // true
console.log(cat instanceof Animal); // true
// Prototype chain is consistent
console.log(Object.getPrototypeOf(dog) === Animal.prototype); // true
Three objects, one constructor, one set of shared methods. That is the entire pattern.
Wrapping Up
The new keyword is not magic — it is four well-defined steps:
Create a new empty object.
Link that object's prototype to
Constructor.prototype.Run the constructor with
thispointing to the new object.Return the new object (unless the constructor returns a different object explicitly).
Constructor functions define the shape of instances — the own properties each one gets. Constructor.prototype holds shared methods that all instances access through the prototype chain. Instances are independent objects sharing a common structure and a common set of inherited behaviours.
This is the foundation of object-oriented programming in JavaScript. ES6 classes make it more approachable, but they are describing the same underlying model. Know the model, and the syntax is just notation.
Comments
Join the discussionNo comments yet. Be the first to comment.
