skip to content

What does a type's run-time self-description contain that lets a renderer build one column per field of an unknown object?

level: juniorimportance: must knowfreq 68%

answer

  1. the type answers questions about itself
  2. shape, not values
  3. members, flags, supertypes, identity
  4. declared type picks the formatter
  5. describes compiled output, not source

basics

~20 s

A loaded type hands back a structured description of itself: its members with their names and declared types, modifier and visibility flags, its supertypes and the contracts it implements, and the type's own run-time identity.

solid answer

~40 s

Run-time type discovery means asking an already-loaded type for a description of itself instead of reading source. That description lists the type's members — fields with their declared types, methods with their parameter and result types — each carrying modifier and visibility flags; it names the supertype the type extends and the contracts it declares; and it carries the type's own identity, which the program can compare against another type. A renderer walks the field list to decide its columns and uses each field's declared type to pick a formatter, so it can handle a record type nobody knew about when the renderer was written. The description is about shape, not values: it says a field named `total` exists and what it is declared to hold, not what any particular row's `total` is.

code

pseudocode · 10 lines
pseudocode
function renderColumns(record)
    description = describeType(record)      // the object's run-time type
    columns = []
    for each field in description.fields
        if field.belongsToType then continue      // not a per-row value
        columns.append({
            header: field.name,
            format: formatterFor(field.declaredType)
        })
    return columns

go deeper

for a junior

Be able to say what a type description contains — members with names and declared types, flags, supertypes, identity — and to state clearly that it describes shape rather than the values in any one object.

for a middle

Explain the split between a field's declared type and the run-time type of the value it holds, and why a renderer's choice between them changes whether two rows of one record type can format differently.

for a senior

Show the judgment a discovery-driven component needs: which members to filter out before they become columns, what to do when a declared type has no formatter, and why the description never matches the source exactly.

for a principal

Frame where reading shape at run time belongs at all: it buys components that work on types written after them, and costs you a contract nobody can see in the source, which is a trade a platform owner should make once and document.

## What asking a type to describe itself means A loaded type is not just machine instructions. The runtime keeps, for every type it has loaded, a structure describing what that type is made of — it needs that structure anyway to lay out instances and resolve members. **Run-time type discovery** is the program reading that same structure: it asks a loaded type what it contains and gets back an ordinary value it can walk with loops and conditionals. The setting for this leaf is a **report renderer**. It is handed an object whose type nobody knew when the renderer was compiled, and it must emit one column per field. It cannot name that type in its own source, so it asks the object which type it is, asks that type for its fields, and builds the column set from the answer. ## What is in the description | part of the description | what it holds | what the renderer does with it | |---|---|---| | members | fields with names and declared types; methods with parameter and result types | one column per field; a candidate accessor per method | | modifier and visibility flags | per member: who may see it, whether it belongs to the type or to each instance, whether it is writable | skip members that belong to the type rather than to a row | | supertypes and contracts | the type this one extends and the contracts it declares | decide whether a specialised formatter applies | | nested members | types declared inside this type | avoid emitting a column for a nested type | | type identity | the value that stands for this type at run time | compare with another type, or key a lookup by it | Two properties of that table matter more than the list: - The description is **of shape, not of values**. It reports that a field named `total` exists and is declared to hold a decimal number. It does not report what one row's `total` is. Pulling a value out of a specific instance is a separate step from discovering that the field exists. - The description is **of the compiled type, not of the source file**. Comments and most local detail are gone; many toolchains drop parameter names unless asked to keep them; and members the compiler generated are present even though nobody typed them. ## Declared type versus run-time type Two different types are in play whenever a renderer reads a field: 1. The **declared type** of the field, which is what the description reports. 2. The **run-time type** of whatever value that field currently holds, which may be a subtype of the declared one. A renderer that picks a formatter from the declared type gets a general formatter and stable columns. A renderer that re-describes the actual value gets a specific formatter but can produce different output for two rows of the same record type. Both are defensible; the choice should be deliberate rather than accidental. The same split applies to the object handed in. The renderer's parameter may be declared as some broad type, but asking the object itself for its type returns the type it was actually created as — which is the one that owns the columns. ## What a discovery-driven renderer actually does 1. Ask the incoming object for its run-time type. 2. Ask that type for its members and filter them: keep instance fields, drop members that belong to the type itself, drop members the compiler generated. 3. For each survivor, take the name as a header and the declared type as the key to a formatter table, with a fallback formatter so an unknown declared type does not abort the report. Step 2 is where most of the interview conversation lives, because a member list is never simply the list a reader of the source would write down. ## What discovery does not give you - It does not give you **values**. Reading a member out of an instance, or calling one, is a separate operation. - It does not give you the **source text**, so anything the compiler discarded is unrecoverable from the description. - It does not by itself give you **access** to members the type keeps hidden — the description can list a hidden member while the runtime still refuses a read of it. - It does not give you **certainty about behaviour**: knowing a method exists says nothing about what it does or whether calling it is safe. The reason interviewers open here is that a whole category of infrastructure — mapping records onto objects, wiring components together, rendering arbitrary data — is built on exactly this: read the shape at run time, then act on it. A candidate who can describe the contents of a type description, and who keeps shape and values separate while describing it, has the foundation the rest of the subject is built on.

  • Does a type description tell you anything about one particular instance?
    No. It describes the type, so every instance of that type produces the same description. The one instance-specific thing discovery gives you is which type the object actually is, which can be a subtype of whatever type the variable holding it was declared as. Anything about the values in that instance's fields comes from reading the instance, not from the description.
  • Why does the description reflect compiled output rather than the source text?
    Because what the runtime loaded is the compiled form; the source file may not even be present. So detail the compiler discarded is gone — comments, and often parameter names unless the build was asked to keep them — while detail the compiler added is present, including members it generated that nobody wrote. A walker that expects a one-to-one match with the source will be surprised in both directions.

It is the parts list stamped on a machine's casing: it tells you which parts the machine has and how each is rated, not what the machine is holding right now.

saying these in an interview costs you the question

  • Thinks the description carries the object's current field values.
  • Believes the runtime reads the source file to answer the question.
  • Assumes a member listing always includes inherited members.
  • Says a field's declared type is always the value's exact type.
  • Confuses discovering that a member exists with being able to call it.