Classes & Objects
A class is a blueprint that bundles data (state) and behavior (methods); an object is a concrete instance of a class living in memory.
Blueprints and instances.
A class defines a type: what data its objects hold (fields/attributes) and what they can do (methods). An object (or instance) is a specific thing created from that class, with its own values.
Analogy: Car is a class (the design). Your specific red Toyota is an object — an
instance of Car with color = "red".
# Problem: related data and behavior are scattered — no single "thing" # that owns the dog's name and knows how to bark. dog_name = "Rex" def bark(name): return f"{name} says woof" print(bark(dog_name)) # works, but nothing groups state + behavior
# Fix: a class is the blueprint; an object is one concrete instance. class Dog: def __init__(self, name): # self = "this instance". Each Dog gets its own name. self.name = name # state (data) lives on the object def bark(self): # behavior (methods) lives with the data return f"{self.name} says woof" rex = Dog("Rex") # construct one object from the class bella = Dog("Bella") # another object — separate state print(rex.bark()) # "Rex says woof" print(bella.bark()) # "Bella says woof"
Constructors and members.
- Constructor — special method that initializes a new object's state.
- Instance members — data/methods that belong to each object.
- Static/class members — shared by all instances of the class.
- this / self — reference to the current object.
Lifecycle and memory.
- Object lifecycle — construction, use, and destruction/garbage collection.
- Memory — objects usually live on the heap; references/pointers on the stack.
- Value vs reference semantics — copying an object vs copying a reference to it.
- Metaclasses / reflection — classes are themselves objects in many languages, enabling introspection and dynamic creation.
Interview Questions
A blueprint/template that defines the structure (attributes) and behavior (methods) for a type of object. It doesn't hold data itself; objects created from it do.
An instance of a class — a concrete entity in memory with its own state (attribute values) and access to the class's behavior. Many objects can be created from one class.
A class is the definition/template (written once); an object is a runtime instance created from it (many can exist). Class is to object as a blueprint is to a house, or a cookie cutter to a cookie.
A special method invoked when an object is created, used to initialize its state (set initial attribute values, acquire resources). Examples: __init__ in Python, a same-named method in Java/C++.
A reference to the current object — the specific instance a method is operating on — used to access that object's own attributes and methods.
Attributes/fields are the data an object holds (its state). Methods are the functions defined in the class that operate on that data (its behavior).
The act of creating an object from a class (e.g. new Dog() or Dog("Rex")), which allocates memory and runs the constructor to initialize the instance.
Encapsulation, Abstraction, Inheritance, and Polymorphism. Together they organize code around objects that bundle state and behavior, hide detail, reuse via hierarchies, and share interfaces.
Instance members belong to each object — every instance has its own copy of instance fields. Static/class members belong to the class itself and are shared across all instances (one copy). Static methods can be called without an instance and can't access instance state directly. Use static for class-wide constants, counters, or utility functions.
Identity asks "are these the same object in memory?" (reference/pointer equality, e.g. is in Python, == on references in Java). Equality asks "do these objects represent the same value?" (logical equality, via equals()/__eq__). Two distinct objects can be equal but not identical. You override equality (and hashing consistently) to compare by value.
A method called when an object is being destroyed, used to release resources (files, connections). In C++ destructors run deterministically when an object goes out of scope. In garbage-collected languages (Java finalizers, Python __del__) they run non-deterministically at GC time, so they're unreliable for cleanup — prefer explicit close/dispose or context managers / try-with-resources / RAII.
A shallow copy duplicates the object but shares references to the nested objects it contains, so mutating a nested object affects both copies. A deep copy recursively duplicates nested objects too, producing a fully independent clone. Choose based on whether shared inner state is desired; deep copies cost more and must handle cycles.
With value semantics, assigning or passing an object copies it, so changes don't affect the original (e.g. C++ objects by value, structs). With reference semantics, you copy a reference/pointer to the same object, so changes are visible through all references (e.g. objects in Java/Python/C#). Misunderstanding this causes bugs like "I changed a copy but the original also changed."
A value object represents a value defined by its attributes (e.g. Money, Coordinate), typically immutable and compared by value. A DTO (Data Transfer Object) is a simple container to move data between layers/services with no behavior. A POJO (Plain Old Java Object) is any simple object without framework requirements. They emphasize data over behavior, unlike rich domain objects.
Hash-based collections (HashMap/HashSet) locate objects by hash code, then confirm with equals. The contract requires equal objects to have equal hash codes. If you override equals but not hashCode, two "equal" objects may land in different buckets, so lookups fail (you can't find a key you inserted). Overriding both consistently (and keeping them based on the same immutable fields) preserves correct collection behavior.
A constructor always creates and returns a new instance of exactly its class, with a fixed name. A factory method is a regular (often static) method that creates objects, but can have a descriptive name, return cached/existing instances, return a subtype, or decide which concrete class to build. Factories add flexibility (naming, caching, polymorphic returns) that constructors can't provide.
In most managed languages, objects are allocated on the heap and accessed via references stored on the stack; primitives and references live on the stack. Heap allocation and pointer chasing hurt cache locality and add GC pressure, so heavy small-object churn can be slow. Techniques to improve it: value types/structs (stack or inline allocation, better locality), object pooling to reuse instances, avoiding unnecessary boxing, and data-oriented layouts (struct-of-arrays) for hot loops. In C++ you control this directly (stack objects, custom allocators). Understanding allocation is key to high-performance OOP.
An object typically stores its fields plus a hidden pointer to its class's virtual method table (vtable) — an array of function pointers for its overridable methods. A virtual/polymorphic call looks up the method in the vtable at runtime (dynamic dispatch), which is why overriding works. Non-virtual methods can be called directly (static dispatch). This adds a small indirection cost and prevents some inlining, which is why some languages make methods non-virtual by default (C++) or use techniques like devirtualization. Interfaces add an extra layer (interface method tables).
In languages like Python, Ruby, and (via Class) Java, a class is itself a first-class object you can inspect and manipulate at runtime. This enables reflection (query methods/fields, invoke by name), dynamic class creation, and metaclasses (classes whose instances are classes, letting you customize class creation — used by ORMs and frameworks to auto-register or add behavior). It's powerful for frameworks and serialization but costs performance and type safety, so it's used sparingly in application code.
Immutable objects can't change after construction, which makes them inherently thread-safe (no synchronization needed), safe to share and cache, safe as map keys, and easier to reason about (no unexpected mutation/aliasing bugs). Design: make all fields final/readonly and set them in the constructor, don't expose setters, defensively copy mutable inputs/outputs (or use immutable collections), and ensure deep immutability of contained objects. "Modifications" return new instances (like Java's String or records). Trade-off: extra allocations, mitigated by structural sharing or builders for complex construction.
Returning a reference to an internal mutable collection/object lets callers modify your object's state behind its back, breaking invariants and encapsulation, and causing aliasing bugs and thread-safety issues. Avoid it by returning defensive copies or unmodifiable/read-only views, exposing immutable value objects, or providing controlled mutation methods that enforce invariants. Likewise, defensively copy mutable arguments you store so the caller can't mutate them later. This preserves the object's control over its own state.
Class: full-featured type with mutable state, behavior, inheritance — for rich domain entities and services. Record/struct: concise, often immutable data carrier with value-based equality (Java records, C# records/structs, Kotlin data classes) — for value objects and DTOs where identity is the data. Enum: a fixed set of named constants (optionally with fields/methods) — for closed sets of values (status, direction), enabling exhaustive handling and type safety over magic strings/ints. Choose the least powerful construct that models the concept: enum for fixed choices, record for immutable data, class for behavior-rich entities.
A god object knows and does too much — a huge class with many responsibilities, fields, and methods that the whole system depends on. It violates single responsibility and high cohesion, becoming a change magnet, hard to test, and a source of merge conflicts and coupling. Refactor by identifying distinct responsibilities and extracting them into focused classes (extract class), grouping related fields/behavior together (cohesion), applying composition/delegation, and introducing interfaces to decouple collaborators. Do it incrementally behind tests, moving one responsibility at a time until each class has a single, clear purpose.
Calling an overridable method from a constructor is dangerous: during base-class construction the derived class's fields aren't initialized yet, but the overridden (derived) method runs — observing an incompletely-constructed object, leading to null/default-value bugs. In Java/C# the derived override executes with uninitialized derived state; in C++ the base version is called (no dynamic dispatch to the not-yet-constructed derived part), which surprises people. Best practice: don't call virtual/overridable methods in constructors; use factory methods, an init step after construction, or make such methods non-virtual/private/final. This is a classic subtle initialization-order pitfall.
In many languages (Java, C++, C#) yes — constructor overloading lets you define multiple constructors with different parameter lists to allow different ways of creating the object. Languages without overloading (like Python) instead use default/keyword arguments or classmethod factories to achieve the same.
An instance variable is a field belonging to an object; it lives as long as the object and holds part of its state. A local variable exists only within a method call (on the stack) and disappears when the method returns. Instance variables are shared across the object's methods; locals are private to one method invocation.
A constructor that creates a new object as a copy of an existing one of the same type (e.g. C++ Foo(const Foo& other)). It defines how the object is duplicated — crucial when the object owns resources (pointers, handles), where the default member-wise copy would cause shared ownership/double-free bugs, so you implement a deep copy (the rule of three/five in C++).
A class defined within another class. A static nested class is just a class scoped inside another for organization; an inner (non-static) class holds an implicit reference to an instance of the outer class and can access its members. They're useful for tightly-coupled helpers (e.g. a Node inside a LinkedList, an iterator, or a builder) that shouldn't be exposed at the top level, improving encapsulation and locality. Non-static inner classes can cause memory leaks by keeping the outer instance alive, so prefer static nested classes unless you need the outer reference.
Object slicing happens when a derived-class object is assigned or passed by value to a base-class variable/parameter: only the base-class part is copied and the derived-specific members and the vtable pointer are "sliced off," so polymorphism is lost (base methods run instead of overrides). It's a subtle source of bugs. Avoid it by handling polymorphic objects through references or pointers (ideally smart pointers) to the base class, never by value, and consider deleting the copy operations or making the base abstract.
A class invariant is a condition that must always hold for an object between method calls (e.g. "a BankAccount balance is never negative", "start ≤ end"). A precondition is what must be true for a specific method to be called correctly (e.g. "withdraw amount must be positive and ≤ balance"). The constructor establishes the invariant, and every public method must preserve it while assuming its preconditions. Encapsulation exists largely to protect invariants: by controlling all state changes through methods, the class guarantees no external code can put it into an invalid state.