A program creates several instances, and only afterwards a method is added to their class. In Python, Ruby and Smalltalk those existing instances immediately gain the new behaviour; in C++ and Java they cannot. Explain what that difference tells you about how a class is represented at runtime, and what each side pays for it.
answer
- live class object vs frozen layout
- Python: MRO walk at every access
- __slots__ = opt out of openness
- Java Class = read-mostly mirror
- C++ runtime = vtable + type_info only
basics
~20 sWhether lookup runs through a live, mutable class object or a frozen definition. Python, Ruby and Smalltalk resolve each call through the class at call time, so existing instances gain new methods immediately; C++ and Java fix layout and dispatch when the type is compiled or loaded.
solid answer
~60 sThe word "class" names two different runtime things, and languages pick one. - **Python**: an attribute access walks the instance `__dict__` and then the class's MRO **at every access**, so a function assigned to the class is found by objects created an hour earlier. It pays with dict lookups on the hot path; `__slots__` is the opt-out that buys a fixed layout back and forbids adding attributes. - **Ruby**: classes are reopenable and each object can have its own singleton class, so a patch reaches live objects; the cost is that the patch is global, which is why refinements were added to scope it. - **Smalltalk**: the class is a live object in the image, and adding an *instance variable* triggers migration of already-existing objects rather than a recompile-and-restart. - **Java**: field offsets and the dispatch table are fixed at class load; HotSwap replaces method bodies only. That frozen shape is what makes constant-offset field access and devirtualization possible. - **C++**: no runtime class object exists at all beyond RTTI; adding a member is an ABI break requiring recompilation of every caller.
code
python · 6 linesclass Greeter:
pass
g = Greeter() # instance created first
Greeter.hello = lambda self: "hi"
print(g.hello()) # "hi" -- lookup walks type(g).__mro__ nowgo deeper
Know that in Python or Ruby a class can be changed while the program runs and existing objects notice, while in Java or C++ the class is fixed once compiled or loaded.
Explain the mechanism on both sides: per-access lookup through the class versus fixed field offsets and dispatch tables, and name one cost of each.
Connect it to tooling and frameworks -- runtime attribute generation in ORMs, how JVM mocking must subclass or instrument, what hot reload can and cannot do on each side.
Frame it as an open-world versus closed-world decision: the frozen model is the precondition for whole-program assumptions such as class-hierarchy analysis and AOT compilation, and the live model buys metaprogramming at the cost of every such assumption.
## Two meanings of "class" A class can be a **compile-time template**: a description the compiler uses to decide how large an object is, where each field sits inside it, and which function each call resolves to. Once that is decided, the description can be thrown away; nothing of it needs to survive into the running program. A class can instead be a **runtime value**: an object that lives in memory, that instances point at, and that is consulted every time a message is sent. Almost every language sits at one of these two poles, and the "add a method after instances exist" experiment is the cleanest way to tell which. ## The live-class family In **Python**, `obj.method()` is not a fixed jump. The interpreter looks in the instance's `__dict__`, then walks the method resolution order of `type(obj)`, then applies the descriptor protocol to whatever it found. Nothing about that sequence is decided in advance, so binding a new function onto the class makes it instantly reachable from objects that were created before the assignment. The same openness is why an instance can grow attributes the class never mentioned: per-instance state is literally a dictionary. `__slots__` exists to give that up on purpose -- it declares a fixed set of descriptors and drops the per-instance dict, trading flexibility for memory and speed. **Ruby** works the same way and goes further: classes are open by design (`class String; def blank?; ...; end; end` reaches every string in the process), and every object can be given a *singleton class* holding methods only it responds to. The cost is that a patch has process-wide blast radius, which is exactly why Ruby later added refinements to confine one. **Smalltalk** is the purest case: the program *is* a graph of live objects in an image, classes among them. Editing a method recompiles it into the live class, and instances -- including ones sitting on the stack -- see the change. Even adding an instance variable is handled at runtime by migrating existing objects to the new shape rather than by restarting the program. ## The frozen-definition family **C++** keeps nothing. A class determines member offsets and vtable layout at compile time; at runtime all that remains is a vtable per polymorphic class and a `type_info` record for RTTI. You cannot obtain a mutable class object because there is none, and adding a data member changes the object's size and layout, which breaks binary compatibility with anything already compiled against it. **Java** and **C#** are a deliberate midpoint. At class-load time the JVM fixes field offsets and builds the dispatch table; `java.lang.Class` then exists as a **read-mostly mirror** you can reflect over but cannot extend. Debug-time HotSwap can swap a method *body*, but adding a method or field invalidates the shape the JIT has already assumed. That frozen shape is not a limitation for its own sake -- it is what allows a field read to compile to a constant offset and a call to be devirtualized after class-hierarchy analysis. **Go** removes the question entirely: there are no classes, method sets are fixed at compile time, and nothing may be added to a type outside its defining package. ## Where the difference actually shows up It decides which frameworks are even writable. Ruby on Rails and Django can generate accessors for database columns at import time because a class is a thing you can build and modify while the program runs; the JVM equivalent needs annotation processing, bytecode generation or a build step. It decides how test doubles work: patching a method on a live class is routine in Python and Ruby, while JVM mocking libraries generate a *subclass* or rewrite bytecode because they cannot touch the loaded class. And it decides what "hot reload" can mean -- a Smalltalk image edits itself; a JVM service restarts. ## How to answer it Say the mechanism, not the vibe: *lookup goes through a live mutable class object* versus *layout and dispatch are fixed at compile or load time*. Then name the price on both sides -- dynamic dispatch cost and unpredictable shape on one, recompile-to-change and closed extension on the other. Candidates who describe one family as "more object-oriented" have missed that both are consistent designs aimed at different constraints.
- Python offers __slots__ to give up this openness. What exactly changes, and why would a team want that?Declaring __slots__ replaces the per-instance dictionary with a fixed set of descriptors, so each instance stores only the declared attributes at fixed positions. Memory per object drops sharply and attribute access gets faster, which matters for millions of small objects. In exchange you can no longer attach undeclared attributes to an instance, and subclasses reintroduce a dict unless they also declare slots.
- If classes are frozen at load time, how do JVM mocking and instrumentation libraries still replace behaviour?They never modify the loaded class. They either generate a fresh subclass or proxy that overrides the methods and hand you an instance of that, or they rewrite bytecode before the class is loaded using a java agent and the instrumentation API. Both work around the frozen shape rather than changing it, which is why mocking a class-level or final method needs extra machinery.
A frozen definition is a printed blueprint used once at the factory; a live class is a rulebook the finished machines keep consulting, so amending the rulebook changes machines already in the field.
saying these in an interview costs you the question
- Saying Python "recompiles the class" -- nothing is recompiled; lookup was always dynamic.
- Claiming C++ has a runtime class object you can query for members; RTTI exposes a type identity, not a member list.
- Treating the frozen model as merely a missing feature, with no mention of constant-offset field access or devirtualization.
- Confusing this with prototype-based delegation -- the question is whether the class object is live and mutable, not whether objects inherit from other objects.
- Asserting JVM HotSwap can add methods or fields; it replaces bodies only.