Duplicate Methods in JavaScript Classes: Last One Wins

Oct 5, 2026·22 min read

When two public JavaScript class methods resolve to the same property key on the same target, the later definition replaces the earlier one; JavaScript does not create method overloads.

The Short Answer: The Later Public Method Wins

A duplicate public method is a pair of method definitions in the same class that produces the same property key and uses the same placement. Two instance methods share the prototype target. Two static methods share the constructor target.

Start with two instance methods named format:

class PriceFormatter {
  format(amount) {
    return `first: ${amount}`;
  }

  format(amount, currency = "USD") {
    return `second: ${currency} ${amount.toFixed(2)}`;
  }
}

const formatter = new PriceFormatter();

console.log(formatter.format(12));
console.log(formatter.format(12, "EUR"));
console.log(Object.hasOwn(PriceFormatter.prototype, "format"));
second: USD 12.00
second: EUR 12.00
true

Both calls reach the second format method. The first method is not selected for the one-argument call, and JavaScript does not retain it as an alternative.

JavaScript class methods are properties, not overload signatures.

A method’s parameter list belongs to its function value. It does not take part in the property key, so format(amount) and format(amount, currency) both define the property named "format".

One property slot, updated twiceAfter the firstmethodformat slotfirst functionAfter the latermethodformat slotsecond functionsame keyThe slot stores one function, not a set of overloads.
Different parameters do not create different method slots.

If one function must accept several call shapes, keep one method and inspect its arguments inside that method. Another option is to give the operations distinct names such as formatAmount and formatCurrency. The sections on JavaScript classes and function expressions provide the surrounding class and function model.

Why ECMAScript Replaces the Earlier Method

A public instance method is installed as a property of the class’s prototype object. An ordinary instance returned by new can reach that property through prototypal inheritance unless an own property shadows it. A derived constructor can instead return a replacement object with a different prototype chain.

A public static method goes somewhere else. It is installed on the constructor function itself, which is why you call Report.create() rather than new Report().create(). Static properties and methods covers that placement in detail.

The specification’s ClassDefinitionEvaluation algorithm visits non-constructor class elements in list order and passes the prototype or constructor target to ClassElementEvaluation. For an ordinary public method, MethodDefinitionEvaluation uses DefineMethod to evaluate the property key and create the function, then DefineMethodProperty defines the data property on that target. A later definition of the same key therefore redefines the configurable property.

Say a class contains these definitions:

class Status {
  label() {
    return "draft";
  }

  label() {
    return "published";
  }
}

const status = new Status();
const descriptor = Object.getOwnPropertyDescriptor(
  Status.prototype,
  "label"
);

console.log(status.label());
console.log(descriptor.value === Status.prototype.label);
console.log(descriptor.writable);
console.log(descriptor.enumerable);
console.log(descriptor.configurable);
console.log(Object.hasOwn(status, "label"));
console.log(Object.hasOwn(Status.prototype, "label"));
published
true
true
false
true
false
true

The first definition creates Status.prototype.label. Later, the second definition targets the same object and the same key, so it defines that property again with a different function value.

Only the final property is observable after class evaluation. Its descriptor shows the standard shape of a public class method: writable, non-enumerable, and configurable.

That non-enumerability matters when you inspect an instance. A for...in loop does not list the method, and Object.keys(status) does not find it. The method still exists through the prototype chain.

Object.hasOwn(status, "label") returns false because the instance does not own the method. Object.hasOwn(Status.prototype, "label") returns true because the prototype does.

The rule therefore needs both halves:

  • The property keys must be equal.
  • The definitions must target the same object.

Matching source text is not enough. Different computed expressions can produce the same key, while identical spelling on a static method and an instance method reaches different targets.

A method address has two coordinateskey: formatkey: openprototypeconstructorfirstlatercollisionstaticanother keyanother cellSame column alone is not enough: the row must matchtoo.
A collision requires the same key and the same target.

Where Last One Wins Does Not Apply

“Last one wins” describes duplicate public definitions that JavaScript is allowed to evaluate. It does not mean every repeated name in a class body is accepted.

The main cases separate like this:

Class-body caseResult
Two public methods with the same key and targetThe later method replaces the earlier method
Two special constructor methodsSyntax error
Two private elements with the same private nameSyntax error, except for one matching getter-setter pair
Public getter and setter with the same keyOne accessor property with a getter and a setter
Two instance fields with the same keyThey initialize in source order, so the later field value remains
Instance field and prototype method with the same keyBoth exist; the own field shadows the prototype method
Two static fields with the same keyThey initialize in source order, so the later field value remains
Static field and static method with the same keyThe field wins in either source order because static fields initialize after methods are installed
Static and instance methods with the same keyBoth exist on different objects
A method in a subclass with the same key as a parent methodThe subclass method overrides access through subclass instances

Duplicate constructor methods

The special constructor method initializes a class instance. A class body may contain only one of these methods.

Writing two special constructor definitions is an early syntax error. No class value is produced, and no first constructor exists for a second constructor to replace. The engine’s exact error message can differ, so the portable fact is the syntax error itself.

A computed method named ["constructor"] is different. It is an ordinary computed method, not the class’s special constructor:

class Parcel {
  constructor(id) {
    this.id = id;
  }

  ["constructor"]() {
    return `parcel ${this.id}`;
  }
}

const parcel = new Parcel(17);

console.log(parcel.id);
console.log(parcel.constructor());
console.log(Parcel.prototype.constructor === Parcel);
17
parcel 17
false

The special constructor still initializes id. The computed method later defines the ordinary "constructor" property on Parcel.prototype, replacing the prototype’s usual reference back to the class.

This is valid, but it makes familiar inspection such as parcel.constructor report something unexpected. A descriptive method name avoids that collision.

Duplicate private names

Private names begin with #, as in #token. Within one class, each private name must be unique.

There is one narrow exception: a private getter and private setter may share a name when both are instance members or both are static members. Together they form one private accessor pair.

Two private methods named #load, two private fields named #load, or an instance private getter paired with a static private setter do not use last-one-wins behavior. They are syntax errors.

Public getters and setters

A public getter and setter may share a key because they supply different halves of one accessor property. A later getter replaces only the getter and preserves an existing setter; a later setter likewise replaces only the setter. A later ordinary method converts the property to a data property and removes both accessor functions, while a later getter or setter converts a method property to an accessor whose other half is initially undefined. ESLint’s duplicate-member rule recognizes a matching getter-setter pair as valid.

Here is one accessor with both operations:

class Temperature {
  #celsius = 0;

  get celsius() {
    return this.#celsius;
  }

  set celsius(value) {
    this.#celsius = Number(value);
  }
}

const temperature = new Temperature();
temperature.celsius = "21.5";

const descriptor = Object.getOwnPropertyDescriptor(
  Temperature.prototype,
  "celsius"
);

console.log(temperature.celsius);
console.log(typeof descriptor.get);
console.log(typeof descriptor.set);
21.5
function
function

Reading temperature.celsius runs the getter, while assigning to it runs the setter. The shared name is deliberate because both definitions contribute to the same accessor property.

One accessor, two directionscelsius propertygetter: readsetter:writeread valueassignvalueThe halves cooperate instead of competing.
A getter and setter are two halves of one accessor.

A regular method is a data property whose value is a function. An accessor is a different property kind, with getter and setter functions instead of a value. That distinction belongs in every duplicate-member investigation.

Fields, Static Members, and Other Lookalikes

Several class features repeat a visible name without behaving like two duplicate methods. The deciding question remains the same: what property key is produced, and which object receives it?

Public fields and prototype methods

A public instance field becomes an own property of each constructed instance. An instance method remains on the class prototype.

If a field and method use the same name, the instance field shadows the prototype method regardless of their textual order in the class body:

class Job {
  run() {
    return "prototype method";
  }

  run = "queued";
}

const job = new Job();

console.log(job.run);
console.log(Object.hasOwn(job, "run"));
console.log(Object.hasOwn(Job.prototype, "run"));
console.log(typeof Job.prototype.run);
queued
true
true
function

Both properties exist. job.run finds the instance’s own field first, so it does not continue to Job.prototype.run.

Lookup stops at the nearest propertyjob.runJob instance: own propertyrun = queuedfound — stopprototype linkJob.prototype propertyrun = function
An own field stops lookup before the prototype method.

Calling job.run() would fail because the visible value is the string "queued", not the prototype function. Moving the field above the method does not change where either property is stored.

Duplicate public fields are initialized in their stored order, and each initialization creates an own data property on the instance. A later field with the same key therefore supplies the final visible value on that instance. This resembles last-one-wins behavior, but it happens during instance field initialization rather than ordinary prototype method installation.

Static and instance members

Static and instance members may share the same spelling because they target different objects:

class Report {
  open() {
    return "instance report";
  }

  static open() {
    return "report index";
  }
}

const report = new Report();

console.log(report.open());
console.log(Report.open());
console.log(Object.hasOwn(Report.prototype, "open"));
console.log(Object.hasOwn(Report, "open"));
instance report
report index
true
true

Report.prototype.open and Report.open are separate properties. Neither replaces the other.

Two static methods that resolve to the same key do collide because both are installed on Report. A same-name static field behaves differently:

class FieldFirst {
  static label = "field first";
  static label() {
    return "method";
  }
}

class MethodFirst {
  static label() {
    return "method";
  }
  static label = "field second";
}

console.log(FieldFirst.label);
console.log(MethodFirst.label);
field first
field second

Class evaluation installs both methods before it initializes the recorded static fields. The field therefore replaces the same-name method in either source order. Two static fields instead initialize in source order, so the later field value remains.

Written order does not control the phaseorderSource Afieldthen methodSource Bmethodthen fieldPhase 1install static methodPhase 2initialize static fieldFinal label propertyfield valueBoth source pathsreach one result.
Static fields run after method installation, regardless of source order.

Computed property keys

Square brackets let a class method calculate its property key. This makes collisions harder to spot because the source expressions can look different:

const action = "save";
const pieces = ["sa", "ve"];

class Draft {
  [action]() {
    return "first save";
  }

  [pieces.join("")]() {
    return "second save";
  }
}

const draft = new Draft();

console.log(draft.save());
console.log(Reflect.ownKeys(Draft.prototype).join(", "));
second save
constructor, save

Both expressions produce the string "save", so both methods target Draft.prototype.save. The later function replaces the earlier one.

Symbols use identity rather than their description. Reusing the same symbol creates a collision, while two separately created symbols remain distinct even if their descriptions match:

A symbol is an identity, not its captionSame symbol object used twiceuse 1use 2sharedsymbolone propertylater winsSame description, separate symbol objectsinspectsymbol Ainspectsymbol Bproperty Aproperty B
Symbol identity, not description text, determines collisions.
const shared = Symbol("inspect");
const separate = Symbol("inspect");

class RecordView {
  [shared]() {
    return "shared: first";
  }

  [shared]() {
    return "shared: second";
  }

  [separate]() {
    return "separate";
  }
}

const view = new RecordView();

console.log(view[shared]());
console.log(view[separate]());
console.log(Object.getOwnPropertySymbols(RecordView.prototype).length);
shared: second
separate
2

The two [shared] methods use one symbol key, so the second replaces the first. [separate] uses another symbol and remains available.

Subclass overriding

A subclass method with the same key as a parent method is overriding, not accidental duplication inside one class. The two methods belong to different prototype objects.

class Message {
  render() {
    return "plain message";
  }
}

class AlertMessage extends Message {
  render() {
    return "alert message";
  }
}

console.log(new Message().render());
console.log(new AlertMessage().render());
console.log(Object.hasOwn(Message.prototype, "render"));
console.log(Object.hasOwn(AlertMessage.prototype, "render"));
plain message
alert message
true
true

An AlertMessage instance finds AlertMessage.prototype.render before the search reaches Message.prototype. The parent function still exists and can be reached with super.render() from the subclass.

Override means nearer, not erasedAlertMessage instancenormal lookupAlertMessage.prototyperender → alert messagefound firstprototype linkMessage.prototyperender → plain messagesuperlookupEach prototype still owns its own render property.
Overriding keeps both methods on different prototypes.

That is the normal mechanism described in class inheritance. Duplicate methods inside one class redefine one property; overriding places properties at different levels of the prototype chain.

How to Find and Prevent Duplicate Members

Duplicate public methods are legal JavaScript, but keeping them makes the class misleading. A reader sees two implementations even though only one remains callable after the class is evaluated.

Editors can report the duplicate while you type. As of October 5, 2026, ESLint’s no-dupe-class-members rule reports duplicate class members and the rules reference marks it as enabled by the recommended @eslint/js configuration. A valid public getter-setter pair is not treated as an accidental duplicate.

The rule can compare statically knowable names, including ordinary names and literal computed keys. It cannot generally prove that runtime-dependent expressions such as [makeKey()] and [parts.join("")] produce the same key, so those collisions require review or runtime inspection.

TypeScript also reports duplicate method implementations through its compiler; for example, TypeScript 7.0.2 reports the compiler’s TS2393 duplicate-function-implementation diagnostic. The ESLint rule documentation says TypeScript users can rely on that compiler check rather than enabling the base JavaScript rule for TypeScript syntax.

These tools report a source problem before runtime. Runtime inspection answers a different question: what property did the program actually produce?

Use a small helper when a method appears to have vanished:

function inspectMember(ClassValue, key, isStatic = false) {
  const target = isStatic ? ClassValue : ClassValue.prototype;
  const descriptor = Object.getOwnPropertyDescriptor(target, key);

  return {
    target: isStatic ? "constructor" : "prototype",
    ownsProperty: Object.hasOwn(target, key),
    kind: descriptor
      ? ("value" in descriptor ? "data" : "accessor")
      : "missing",
    valueType: descriptor && "value" in descriptor
      ? typeof descriptor.value
      : "none",
  };
}

class Exporter {
  export() {
    return "text";
  }

  export() {
    return "json";
  }
}

const result = inspectMember(Exporter, "export");

console.log(result.target);
console.log(result.ownsProperty);
console.log(result.kind);
console.log(result.valueType);
console.log(new Exporter().export());
prototype
true
data
function
json

The helper checks the exact target with Object.hasOwn, then reads the descriptor without invoking the property. It identifies export as an own data property of the prototype whose value is a function.

If both behaviors are needed, rename the operations or combine them behind one documented method that dispatches from explicit input. Do not leave an intentional duplicate as a source-order trick. Object methods and this explains how method calls receive their object, while JavaScript Fundamentals develops the property, function, prototype, and class rules as one sequence.

A Debugging Checklist

When the method being called is not the method visible near the top of a class, inspect the property JavaScript created rather than counting declarations.

  1. Determine the final property key. For a computed name, evaluate the expression and check whether it produces a string or a symbol.
  2. Identify the target. Instance methods use the prototype; static methods use the constructor function; instance fields become own properties of constructed objects.
  3. Check ownership with Object.hasOwn(instance, key), Object.hasOwn(ClassValue.prototype, key), and Object.hasOwn(ClassValue, key).
  4. Read the descriptor with Object.getOwnPropertyDescriptor. A value indicates a data property, while get or set indicates an accessor.
  5. Check the prototype chain. A subclass method may override a parent method without deleting the parent’s property.
  6. Inspect computed names for collisions that different source expressions conceal.
  7. Inspect the code that actually runs after compilation or bundling. Build output can differ from the source being read, so locate the emitted class or equivalent property definitions before blaming runtime dispatch.

The narrow rule survives every check for ordinary public methods: when two such methods produce the same key on the same target, the later method remains. Accessor halves update separately; instance fields initialize on each instance and can shadow prototype methods; static fields initialize after methods are installed and beat a same-name static method in either source order. Constructors and private names can fail before evaluation, and inheritance keeps both methods on different prototypes.

Frequently asked questions

What happens when a JavaScript class has two methods with the same name?
When two public methods resolve to the same property key on the same class target, the later method replaces the earlier one. JavaScript keeps one property and does not create overloads.
Can a JavaScript class have two constructors?
No. A class body containing more than one special constructor method is a syntax error, so the class cannot be created.
Can static and instance methods have the same name?
Yes. An instance method is stored on the class prototype, while a static method is stored on the constructor function. The names match, but the methods belong to different objects.
Do JavaScript class methods support overloading by parameter count?
No. Parameter counts and parameter types do not create separate method slots in JavaScript. If two public methods use the same key on the same target, only the later property remains.
How can I detect duplicate class methods?
As of October 5, 2026, use editor diagnostics, ESLint's [no-dupe-class-members](https://eslint.org/docs/latest/rules/no-dupe-class-members) rule, or TypeScript's duplicate-implementation check. Runtime-dependent computed keys may require review or runtime inspection with Object.getOwnPropertyDescriptor and Object.hasOwn.