All chapters in this track
- 01Chapter 1: Why Object Orientation Exists
- 02Chapter 2: Classes, Objects, and Instances, Precisely Defined
- 03Chapter 3: The Four Pillars I: Encapsulation
- 04Chapter 4: The Four Pillars II: Abstraction
- 05Chapter 5: The Four Pillars III: Inheritance
- 06Chapter 6: The Four Pillars IV: Polymorphism
- 07Chapter 7: Composition vs. Inheritance
- 08Chapter 8: Interfaces and Abstract Classes: When to Use Which
- 09Chapter 9: SOLID, Part 1: Single Responsibility & Open/Closed
- 10Chapter 10: SOLID, Part 2: Liskov Substitution, Interface Segregation, Dependency Inversion
- 11Chapter 11: Basic UML for Communicating Design
- 12Chapter 12: Anti-Patterns: God Objects, Tight Coupling, Deep Hierarchies, the Fragile Base Class, Anemic Domain Models
- 13Chapter 13: OOP Idioms Across TypeScript and Python — and Where OOP Is the Wrong Tool
- 14Chapter 14: When OOP Breaks Down: Real-World Failure Modes
- 15Chapter 15: Design Exercise: Parking Lot System
- 16Chapter 16: Design Exercise: Library Management System
- 17Chapter 17: Design Exercise: Elevator Controller
- 18Chapter 18: OOP in Interviews: What a Strong Answer Looks Like
Chapter 2 of 18
Chapter 2: Classes, Objects, and Instances, Precisely Defined
Why this matters
"Class," "object," and "instance" get used loosely enough in everyday conversation that engineers often can't state precisely what each one means — and then struggle in design discussions or interviews when the distinction actually matters (for example, explaining static vs. instance members, or why a class itself can be an object in some languages). Getting the vocabulary exact costs ten minutes and pays off every time you need to reason precisely about shared vs. per-object state.
The core concept
[BEGINNER] Three terms, precisely:
- Class — a blueprint. It defines what fields (state) and methods (behavior) something of this type will have. A class itself does not hold any particular object's data; it holds the shape that data will take.
- Object — a runtime entity that has state and behavior, created according to some class's blueprint. "Object" is the general term for "a thing with identity, state, and behavior" — in class-based languages, every object is an instance of some class.
- Instance — the same thing as an object, but the word is used specifically to emphasize its relationship to a class: "this object is an instance of that class." You'll hear "object" when talking about the entity generally, and "instance" when the class it came from is the relevant point.
Concretely:
class Dog { // <-- the class: the blueprint
constructor(public name: string) {}
bark(): string { return `${this.name} says woof`; }
}
const rex = new Dog("Rex"); // <-- rex is an object, and an instance of Dog
const fido = new Dog("Fido"); // <-- a second, independent instance of Dog
rex and fido are two separate objects. They share the same class (the same blueprint — the same bark method, the same shape of state), but each has its own independent name. This is the essential relationship: the class is shared, the instance's state is not.
class Dog:
def __init__(self, name: str) -> None:
self.name = name
def bark(self) -> str:
return f"{self.name} says woof"
rex = Dog("Rex")
fido = Dog("Fido")
[IN PRACTICE] "Instance" comes up constantly in phrases you'll use daily: instance variable (state that belongs to one object, not shared across all objects of the class), instance method (a method that operates on one particular object's state, via this/self), as opposed to a static or class-level member, which belongs to the class itself and is shared by every instance:
class Dog {
static species = "Canis familiaris"; // shared by every Dog instance
name: string; // per-instance — each Dog has its own
constructor(name: string) {
this.name = name;
}
}
console.log(Dog.species); // accessed on the class, not an instance
console.log(new Dog("Rex").name); // accessed on the instance
class Dog:
species = "Canis familiaris" # class attribute, shared
def __init__(self, name: str) -> None:
self.name = name # instance attribute, per-object
print(Dog.species)
print(Dog("Rex").name)
A common point of confusion: in Python, classes are themselves objects (instances of type), which is why you can inspect, pass around, and even dynamically create classes at runtime. TypeScript/JavaScript has a related but distinct idea — a class is a value (you can pass Dog itself around as a reference to the constructor function) — but its type system's notion of "class" is erased at compile time; at runtime, a JS class is just a function with a prototype.
Identity vs. equality
[SENIOR INSIGHT] A subtlety that trips people up in interviews and in real bugs alike: two objects can have identical field values and still be different instances. Identity asks "are these the same object in memory?" Equality asks "should these be treated as the same value?" — and that answer is something you define, not something the language decides for you by default.
class Point {
constructor(public x: number, public y: number) {}
equals(other: Point): boolean {
return this.x === other.x && this.y === other.y;
}
}
const a = new Point(1, 2);
const b = new Point(1, 2);
console.log(a === b); // false — different objects (identity)
console.log(a.equals(b)); // true — same value (equality, as we defined it)
from dataclasses import dataclass
@dataclass
class Point:
x: int
y: int
# @dataclass auto-generates __eq__ based on field values
a = Point(1, 2)
b = Point(1, 2)
print(a is b) # False — different objects (identity)
print(a == b) # True — dataclass-generated value equality
Python's plain classes default to identity-based __eq__ (like TypeScript's ===), which is why @dataclass (or a manually written __eq__) matters the moment you want two instances to compare equal by value. This distinction becomes critical the moment objects are stored in sets, used as dictionary/map keys, or compared after being deserialized from a database or API response — a fresh instance rebuilt from the same data is never identity-equal to the original, only value-equal if you defined it that way.
Common mistakes
- Using "class" and "object" interchangeably in design discussions. Saying "I'll create a class for each order" when you mean "I'll create an instance for each order" causes real confusion — you create one class and many instances of it.
- Putting per-request or per-user state in a static/class-level field. This is a common source of subtle, hard-to-reproduce bugs: static state is shared across every instance (and in a server context, potentially across every request), so putting a user's session data there leaks it across users.
- Relying on default equality without thinking about it. Forgetting that plain objects/classes compare by identity by default leads to bugs like
if (usersList.includes(currentUser))silently always returningfalsewhencurrentUserwas freshly deserialized rather than the exact same in-memory object. - Assuming a class can't be treated as a value. In both Python and TypeScript/JavaScript, classes are (or behave like) first-class values — they can be stored in variables, passed to functions, and put in collections. Not knowing this leads to workarounds (giant if/else chains keyed by string names) where passing the class itself would be simpler (this is developed further in Chapter 9).
Checklist: reasoning correctly about classes, objects, and instances
- State the relationship precisely: "one class, many instances." If you find yourself wanting a separate class per data value, you almost certainly want instances of one class instead.
- Ask whether a field belongs to the class or to the instance. Shared, class-level truths (a species name, a configuration constant, a counter of how many instances exist) are static/class-level. Anything that varies per object is an instance field.
- Never store per-request or per-user mutable state in a static field, especially in server-side code — static state usually lives for the process lifetime and is shared across everything running in that process.
- Decide equality semantics deliberately, don't inherit them by accident. If two instances should be treated as equal by their values, define that explicitly (
equals()/__eq__, or a language feature like Python's@dataclass, or a record/value type where the language supports one). - Remember classes are values in most modern languages. When you need behavior to vary by type, consider passing the class/constructor itself around before reaching for a large conditional.
Discussion / interview questions
-
"What's the difference between a class and an object?" Strong answer: Class = blueprint defining shape and behavior; object = a runtime entity built from that blueprint, holding its own state. Gives a code example showing one class producing two independent instances.
-
"What's the difference between an instance variable and a static/class variable?" Strong answer: Instance variables are per-object and independent across instances; static/class variables are shared by all instances of the class and live on the class itself. Gives an example of a bug caused by confusing the two (e.g., a shared counter mistakenly used for per-object state, or vice versa).
-
"If two objects have identical field values, are they equal?" Strong answer: Not necessarily — distinguishes identity (same object in memory) from equality (should be treated as the same value), and states that default equality is identity-based in most class-based languages unless you define value equality yourself.
-
"Are classes themselves objects?" Strong answer: In Python, yes — classes are instances of
type, which is why introspection and dynamic class creation work. In TypeScript/JavaScript, classes are first-class values (you can pass them around) even though the type-level "class" concept is erased at compile time. A strong answer doesn't need deep runtime internals, just the practical implication: you can pass a class around like any other value. -
"Describe a bug you've seen (or could imagine) caused by confusing static and instance state." Strong answer: A concrete scenario — e.g., a web server handler class with a static field intended as a "per-request cache" that actually leaked one request's data into another request's response because it was shared across all instances/requests on that process.
Chapter summary / cheat sheet
- Class = blueprint. Object = a runtime entity with state and behavior. Instance = "object," said with emphasis on which class produced it.
- One class, many instances — each instance has independent state; the class's methods and static/class-level fields are shared.
- Identity (
===/is) asks "same object in memory?" Equality (equals()/__eq__) asks "same value?" — and value equality must usually be defined explicitly. - Never put per-request or per-user mutable state in a static/class-level field — it's shared across every instance, and in servers, potentially every request.
- Classes are first-class values in Python and TypeScript/JavaScript — you can pass them around instead of writing large conditionals keyed by type.