Problem overview
A class bundles values with the questions you can ask about them. Rectangle remembers a width and a height, and answers three questions: its area(), its perimeter(), and whether it isSquare(). Each object keeps its own sizes, so two rectangles never interfere with each other.
For new Rectangle(3, 4) the area is 3 · 4 = 12, and walking around the sides gives 3 + 4 + 3 + 4 = 14. The sizes differ, so it is not a square. For new Rectangle(5, 5) the area is 25, the perimeter 20, and it is a square.
Approach
There is nothing to search or sort here. The class needs two pieces of state, width and height, and each method is a one-line formula over them. Every method runs in constant time because it only reads two numbers.
Step by step
- Declare the fields. Inside the class body,
width: number;andheight: number;say that everyRectangleobject carries these two values. - Fill them in the constructor. The parameters have the same names as the fields, so
this.width = width;reads as "the field of this object gets the parameter". - Area. Multiply the two fields.
- Perimeter. Twice the sum of the sides:
2 * (this.width + this.height). The parentheses make the addition happen first. - Square test. A comparison is already a boolean, so
this.width === this.heightcan be returned directly, with noif.
The solution in TypeScript
class Rectangle {
width: number;
height: number;
constructor(width: number, height: number) {
this.width = width;
this.height = height;
}
area(): number {
return this.width * this.height;
}
perimeter(): number {
return 2 * (this.width + this.height);
}
isSquare(): boolean {
return this.width === this.height;
}
}
A few details:
this. Inside a method,thisis the object the method was called on. Without it,widthwould mean the constructor's parameter, which no longer exists once the constructor ends.- Types are only for the compiler.
numberannotations are checked when you compile and then erased: the JavaScript that runs has no types. ===compares without converting. For two numbers it is true exactly when they are equal.
Using the class
const r = new Rectangle(3, 4);
console.log(r.area(), r.perimeter(), r.isSquare());
const s = new Rectangle(5, 5);
console.log(s.area(), s.perimeter(), s.isSquare());
const a = new Rectangle(2, 10);
const b = new Rectangle(10, 2);
console.log(a.area(), a.perimeter(), a.isSquare());
console.log(b.area(), b.perimeter(), b.isSquare());
console.log(a.width, b.width);
This prints:
12 14 false
25 20 true
20 24 false
20 24 false
2 10
The lesson's tests print the booleans as True and False; TypeScript itself prints true and false. The last line shows that a and b each keep their own width. Turning a rectangle on its side changes neither its area nor its perimeter.
Why a class instead of functions
You could write area(width, height) as a plain function and pass the two numbers every time. That works for one rectangle, but the caller must carry the sizes around and keep each pair together. A class moves that bookkeeping into the object: you create the rectangle once, and afterwards you only ask it questions. The methods get the numbers from this, so you never pass them again.
When the code is compiled for ES2022, the class stays a JavaScript class. The two field declarations remain as fields of the object, and the types disappear. After new Rectangle(3, 4), the object's own properties are exactly width and height, in that order. The methods live on the class and are shared by all rectangles.
Testing
Try the smallest and the largest sizes in the constraints:
new Rectangle(1, 1)gives area 1, perimeter 4, andtrue.new Rectangle(1, 10000)gives area 10000, perimeter 20002, andfalse.new Rectangle(10000, 10000)gives area 100000000, perimeter 40000, andtrue.
Even the largest area, 100 million, is far below the largest integer a number holds exactly (2 to the power 53), so no overflow worries here.
Another way: parameter properties
TypeScript has a shortcut for the pattern "declare a field, then copy the parameter into it". Putting a modifier such as public in front of a constructor parameter declares the field and assigns it in one go:
class Rectangle {
constructor(public width: number, public height: number) {}
area(): number { return this.width * this.height; }
}
const r = new Rectangle(3, 4);
console.log(r.width, r.height, r.area());
This prints 3 4 12. The lesson builds the longer form on purpose, so that you see the three separate things a constructor does: receive the arguments, name the fields, store the values.
Edge cases and common mistakes
- Forgetting
this.. Writingreturn width * height;insidearea()refers to names that don't exist there, and the compiler reports it. - Perimeter without parentheses.
2 * this.width + this.heightdoubles only the width. For a 3 by 4 rectangle it gives 10 instead of 14. - Using
==or aniffor the square test.===is the idiomatic comparison for numbers, and anifthat returnstrueorfalseonly repeats the comparison. - Sharing state. Keep the sizes in fields, not in variables outside the class, or every rectangle would see the same numbers.
Key points
- A class describes what every object of that kind has: fields for the data and methods for the questions.
- The constructor runs once for each
newand gives the object its starting values. - Each object has its own copy of the fields, so objects built from the same class stay independent.
- A method that only reads fields and does one calculation, like all three here, runs in constant time.
- Returning a comparison directly is clearer than wrapping it in an
ifthat returnstrueorfalse.
