In most class-based languages a call like obj.method() is resolved from the object's actual class, while a read like obj.field is resolved from the declared type of the expression obj. Why does state bind to the declared type, and how do the languages that do let a subtype substitute state actually achieve it?
answer
- offset has nowhere to dispatch; a table slot does
- hiding, not overriding — one object, two slots
- Swift: override an inherited stored property with a computed one
- C#: field to property = binary break, decide up front
- Python property is a data descriptor — beats the instance dict
basics
~20 sA field read is a load at an offset the compiler fixes from the declared type, so there is nothing to reroute. A virtual call is a table lookup, which can be rerouted. Substitutable state must therefore be method-shaped: properties, accessors, descriptors.
solid answer
~60 sA field access compiles to a load at an offset chosen from the **declared** type of the expression — on the JVM the `getfield` instruction names the owning class directly in the constant pool. There is no indirection, so nothing can redirect it. A virtual call reads a fixed *slot* of the object's per-class dispatch table, and the slot's contents differ per class. Field hiding follows directly: with a shadowed name, one object has two slots and `((Base) d).x` and `d.x` read different ones. Escaping this means making state method-shaped, and languages differ on how late you can decide. C++ cannot at all — a derived member only hides, and by-value assignment to a base slices the derived part away. C# fields are never virtual, and promoting a public field to a property breaks already-compiled callers. Kotlin's `val` is already a getter, so `open val` overrides for real. Swift lets a subclass override an inherited *stored* property with a computed one. Python has no offsets — lookup walks descriptors and the MRO, so `@property` can intercept later.
code
text · 10 linesclass Base { field name = "base"; method speak() -> "base speaking" }
class Deriv : Base { field name = "deriv"; override speak() -> "deriv speaking" }
d : Deriv = new Deriv()
b : Base = d // ONE object, two declared types
read d.name -> "deriv" // offset chosen from declared type Deriv
read b.name -> "base" // offset chosen from declared type Base
call d.speak() -> "deriv speaking" // slot read from the object's table
call b.speak() -> "deriv speaking" // same object => same behaviourgo deeper
Recall the rule and the reason: fields are resolved from the declared type of the expression, methods from the object's actual class. Be able to trace which value a base-typed variable reads when both classes declare the same field name.
Explain the mechanism (fixed offset vs dispatch-table slot), demonstrate field hiding with a cast, and name at least one language where an accessor makes state substitutable and one where it is impossible.
Connect it to production consequences: what proxies, mocks and lazy-loading ORMs can and cannot intercept, and why converting a public field to a property is a binary break in some ecosystems and a non-event in others.
Frame it as an API-evolution decision. Decide per language whether exposing state as a plain property is reversible, and set a house rule for library boundaries that matches the language rather than importing a convention from another ecosystem.
## The one-line mechanism When a language compiles an object to a fixed memory layout, an instance is a block of memory and each declared field sits at a known offset inside it. The compiler computes that offset from the **declared** (static) type of the expression you wrote, and emits a load. On the JVM the `getfield` instruction names the owning class directly in the class file's constant pool; in C++ it is literally a base pointer plus a constant. A virtual call is different in kind. The object carries a pointer to a per-class dispatch table (vtable, method table, itable). The call site reads a fixed *slot* of that table, and the *contents* of the slot differ per class. That single indirection is the whole of subtype polymorphism. State has no such indirection, so there is nothing to substitute. So "fields are not polymorphic" is not a rule someone sat down and chose; it falls out of representing state as layout. The useful corollary is: **anything a subtype should be able to substitute must be reachable through an indirection, which in practice means it must be method-shaped.** ## Field hiding, and why it burns people Because the binding uses the declared type, a subclass that declares a field with a name the base already uses does not replace it — it **hides** it. Both fields exist in the object simultaneously, and which one you read depends only on how the reading expression is typed. In Java and C# `((Base) d).x` and `d.x` read two different storage slots of one object; `super.x`/`base.x` reaches the hidden one. C++ behaves the same way and adds *slicing*: assigning a derived object by value into a base-typed variable copies only the base sub-object, so the derived state is not merely unreachable, it is gone. Methods have the opposite property: one object, one behaviour, regardless of the reader's type. ## Making state substitutable: three timing regimes Bertrand Meyer's *uniform access principle* says a client should not be able to tell whether a value is stored or computed. Languages honour it at three different points in time, and that choice decides whether "expose a field" is a safe decision. **Opt in at compile time (C#, and Java by convention).** Fields are never virtual and cannot be intercepted; only a property declared `virtual` can be overridden. Converting a published public field into a property is source-compatible but **binary**-incompatible — assemblies already compiled against the field emit a field-access instruction and break — and a field can be passed by `ref`/`out` where a property cannot. The escape hatch must therefore be reserved up front, which is exactly why .NET guidance forbids public fields in libraries and why the Java ecosystem grew getters everywhere. **Accessors by default (Kotlin, Swift).** In Kotlin, `val`/`var` are accessors from the caller's side and the backing field is an implementation detail, so marking a `val` as `open` makes overriding it genuine dispatch (a subclass may even widen an `open val` to a `var`). Swift goes further: a subclass may override an inherited **stored** property with a computed one, or attach `willSet`/`didSet` observers to an inherited property. Both languages let you start with something that looks like a field and later make it computed without touching a single caller. **Always, at run time (Python, Ruby, Objective-C).** Python has no offsets worth speaking of: `obj.x` consults descriptors found on the type along the MRO and the instance dictionary, so a class attribute can intercept the read entirely and `__getattr__` can synthesise attributes that were never declared. Ruby is stricter still — instance variables are unreachable from outside, every read goes through a method, so a subclass overrides a reader like any other method. Objective-C `@property` synthesises accessor methods reached by message send, interceptable even at run time. ## The Python caveat worth knowing Uniform access in Python is real but not free. A `property` is a **data descriptor**: it always defines `__set__`, which raises when no setter was supplied, and data descriptors defined on the type take precedence over the instance dictionary. So if a base `__init__` does `self.balance = 0` and a subclass declares `balance` as a getter-only `@property`, merely *constructing* the subclass raises an `AttributeError` about the missing setter — before any caller reads anything. The conversion is caller-transparent, not constructor-transparent. Give the override a setter, or do the stored-to-computed conversion inside the class that owns the assignment. ## What the asymmetry costs in real systems 1. **Interception tools only see dispatch.** Dynamic proxies, mocking libraries, lazy-loading ORM wrappers and instrumentation layers work by subclassing and intercepting calls. A public field is invisible to all of them — an ORM cannot lazily populate it through a proxy, a mock cannot fake it. (ORMs that map fields do it by reflection or bytecode weaving, not by dispatch.) In Python all three work on plain attributes, because attribute access itself is interceptable. 2. **API evolution is reversible in some languages and not others.** Advice copied between ecosystems without this context is how mandatory getters arrived in languages that never needed them. 3. **Buying dispatch buys its hazards** — an overridable accessor invoked during base-class construction runs the subclass version before the subclass's storage exists — but that is a consequence of the indirection, not of the binding rule itself. ## The rule to remember Expose state as an accessor — or work in a language where every access already is one — if a subtype, a proxy, an ORM or a test double should ever be able to substitute it. Never rely on a shadowed field name for meaning: it is one object with two slots and the reader's declared type picks. Where the language lets you change your mind later (Kotlin, Swift, Python, Ruby), a plain property declaration is the honest one and a hand-written getter that only returns the field is ceremony.
- A teammate says "just make the field protected so subclasses can override it." What do you tell them?Access modifiers control visibility, not binding — a protected field is still resolved from the declared type, so a subclass redeclaring the name hides rather than overrides and the object ends up carrying both slots. The only way a subtype substitutes the value is through something with a dispatch slot: an overridable accessor, or a language where attribute access is itself a lookup. Protected mutable state also widens the subclass contract, which is a separate reason to prefer an accessor.
- Why can't a mocking library or a lazy-loading ORM proxy intercept a public field?Both work by producing a subclass or a generated implementation and overriding what dispatches; a field read compiles to a direct load, so there is no call to intercept. The proxy's own field is simply never populated, and the caller reads a default value. Frameworks that do map fields reach them by reflection or bytecode weaving instead, which is why field-access ORM mappings and lazy loading interact badly on the proxy path.
- In which languages can you turn a stored value into a computed one after the API is published, without breaking callers?Kotlin, Swift, Python and Ruby, because every read already goes through an accessor or a runtime lookup. C# and Java cannot for a public field: source still compiles, but assemblies or class files compiled against the field break, and in C# the field can be passed by ref while a property cannot. Swift is the outlier at the inheritance level, since a subclass may override an inherited stored property with a computed one.
A field is a street address stamped on the envelope when the letter is written: it goes where the writer wrote, no matter who lives there now. A virtual method is a phone extension — the switchboard decides at connect time who actually picks up.
saying these in an interview costs you the question
- Saying fields are "overridden" in Java/C#/C++ — they are hidden, and both slots coexist in the object.
- Believing an access modifier (protected/public) affects binding; it affects visibility only.
- Claiming a field read on a base-typed variable returns the subclass's value because the object "really is" the subclass.
- Assuming Kotlin's `val` is a field — it is an accessor, which is exactly why `open val` can be overridden.
- Assuming uniform access is free in Python: a getter-only @property in a subclass breaks a base constructor's assignment, because property is a data descriptor that always defines __set__.