Don't Write Traditional Getter and Setter Methods in JavaScript and TypeScript

Say you want to create a class to represent a point in two-dimensional space. If you come from a certain background, your immediate instinct may be to create some private properties and getter/setter methods:

class Point2D {
private x: number;
private y: number;

getX() {
return this.x;
}
setX(x: number) {
this.x = x;
}
getY() {
return this.y;
}
setY(y: number) {
this.y = y;
}
}

This feels productive. You're writing code, after all! If you're feeling especially diligent, you might even write JSDoc comments for each of these methods. Your editor might even include shortcuts to write all these getters and setters for you.

Here's some code that uses that class:

const pt = new Point2D();
pt.setX(3);
pt.setY(4);
console.log(pt.getX(), pt.getY()); // logs 3, 4

The downside is that this is a lot of boilerplate code that doesn't do very much. Why write the getters and setters rather than this?

class DirectPoint2D {
x: number;
y: number;
}

const pt = new DirectPoint2D();
pt.x = 3;
pt.y = 4;
console.log(pt.x, pt.y); // logs 3, 4

At least in Java, the answer is that getters and setters encapsulate the implementation of the class and give it much greater flexibility to evolve in the future.

For example, what if you realize that for some reason it's much better to use polar coordinates internally? With the getters and setters, it's no trouble to reimplement the old API using the new internal representation:

class Point2D {
private r: number;
private theta: number;

getX() {
return this.r * Math.cos(this.theta);
}
getY() {
return this.r * Math.sin(this.theta);
}
setX(x: number) {
const y = this.getY();
this.r = Math.sqrt(x**2 + y**2);
this.theta = Math.atan2(y, x);
}
setY(y: number) {
// ... similar
}
}

Users of the Point2D class will be completely oblivious to this internal change. The code above works without change. Contrast this with DirectPoint2D. You can't make an analogous change to this version because the internals are exposed. You can't get rid of the x and y properties without making a breaking change to the API. You're stuck.

That's the story in Java, anyway, and it was also the story for JavaScript in the 1990s and early 2000s. If you use very old JS libraries (or libraries written by recent transplants from Javaland), you may still run across these sorts of getter and setter methods. After publishing my post about Google's Closure Compiler I learned that one of its goals was to inline simple methods like these.

But getter and setter methods like these are not a good idea in modern JavaScript or TypeScript. The reason is that back in 2009, ES5 introduced a new syntax for get and set methods that entirely eliminates this problem with direct property access.

Here's how you'd migrate DirectPoint2D to a polar coordinates representation using getter and setter methods:

class DirectPoint2D {
r = 0;
theta = 0;

get x() {
return this.r * Math.cos(this.theta);
}
set x(x: number) {
const y = this.y;
this.r = Math.sqrt(x**2 + y**2);
this.theta = Math.atan2(y, x);
}

get y() {
return this.r * Math.sin(this.theta);
}

set y(y: number) {
// ... similar to set x
}
}

Usage looks exactly as it did before:

const pt = new DirectPoint2D();
pt.x = 3;
pt.y = 4;
console.log(pt.x, pt.y); // logs 3, 4

What were direct property accesses before have become method calls. But the syntax is character-for-character identical, so the caller need not be aware that anything has changed. The public properties are no longer a constraint on your class design.

The takeaway here is that it's OK to use a public property on a class in JavaScript and TypeScript. You may read warnings in books and online about how this is a bad practice, but this advice has more to do with specific limitations of Java and old-school JS than it does with modern JavaScript and TypeScript. When you write getter and setter methods in JavaScript, you're working around a problem that no longer exists.

Simple getter and setter methods are a code smell in JavaScript and TypeScript. Don't write them! If you see them in a code review, suggest replacing them with a public property and send your coworker here for an explanation of why.

One cautionary note: don't go too crazy with get and set. When you read code like pt.x = 3, you expect that this will do something like setting the x property of pt to 3. Of course, with a set method, it could do anything. It could set y instead, or it could even issue a network request. But to avoid confusion and surprise, it's best if paired get and set methods get and set the same thing, at least conceptually.

Here's a complete playground link for the last example if you want to give it a try.

Like this post? Consider subscribing to my newsletter, the RSS feed, or following me on Twitter.
Effective TypeScript Book Cover

Effective TypeScript shows you not just how to use TypeScript but how to use it well. Now in its second edition, the book's 83 items help you build mental models of how TypeScript and its ecosystem work, make you aware of pitfalls and traps to avoid, and guide you toward using TypeScript’s many capabilities in the most effective ways possible. Regardless of your level of TypeScript experience, you can learn something from this book.

After reading Effective TypeScript, your relationship with the type system will be the most productive it's ever been! Learn more »