skip to content

A shortcut table is filled with table[key] = save(), and the document saves at startup - what was stored?

level: juniorimportance: must knowfreq 74%

answer

  1. one character changes everything
  2. name alone versus name with parentheses
  3. what does the entry actually hold
  4. a call yields a result, not behaviour
  5. store the function, defer the call

basics

~20 s

The entry stored the result of calling save, not the function itself: the parentheses invoked it while the table was being built. Writing table[key] = save stores the function value and leaves the call for key-press time.

solid answer

~40 s

`save` and `save()` are different expressions. The bare name evaluates to **the function value**; adding parentheses makes an application that runs the body immediately and evaluates to whatever the body returned. So `table[key] = save()` saved the document during table construction and put the return value in the entry, while `table[key] = save` puts the function in the entry and moves the call to the dispatcher, which looks the key up and then applies what it found. If the behaviour needs extra steps at press time, store an inline literal whose body contains the call - the parentheses inside a literal belong to a body that has not run yet, which is the opposite of a call at the assignment site.

code

pseudocode · 12 lines
pseudocode
function save()
    write buffer to disk
    return "saved"
end

table["ctrl+s"] = save()   // wrong: runs now, entry holds "saved"
table["ctrl+s"] = save     // right: entry holds the function itself

on key press k
    action = table[k]
    action()               // the only place the body runs
end

go deeper

for a junior

Be able to say out loud that a bare name denotes the function while a name with parentheses denotes the result of running it, then point at the line that ran too early.

for a middle

Explain exactly what lands in the entry in each version and where the single call site moves to, showing the dispatcher looking a key up and then applying what it found.

for a senior

Describe how you would catch this in a real codebase: an effect firing during construction of a registry, an entry that is not callable, a table that misbehaves only on first press.

for a principal

Consider whether registries in the codebase should accept only callable values and reject anything else at registration, trading a little rigidity for failures that surface at the mistake.

## A name and a call are two different expressions When functions are values, the name `save` is an ordinary expression that evaluates to **the function itself**, exactly as `3` evaluates to a number. Adding parentheses makes a different expression: `save()` is an **application**. It evaluates the name to a function value, runs the body, and then evaluates to whatever the body returned. One expression yields behaviour; the other yields a result. They differ in kind, not in style, and no language treats the parentheses as decoration on a name. A keyboard-shortcut table is where the difference becomes impossible to hide, because the entire point of the table is to hold behaviour that has **not run yet**. The dispatcher needs something it can apply when a key is pressed. `table[key] = save()` puts the result of an already-finished call there instead. ## What each spelling puts in the entry | what was written | what the entry holds | when the body runs | |---|---|---| | `table[k] = save` | the function value itself | once per key press | | `table[k] = save()` | whatever `save` returned | once, while the table is built | | `table[k] = function() save() end` | a fresh function value that calls `save` | once per key press | The third row is the one people misread. An inline literal that *contains* a call is not itself a call: its parentheses sit inside a body that nothing has executed. The parentheses in `save()` at the assignment site execute now. ## Why the symptom looks like a startup bug Because the call happens while the table is being constructed, every observable effect of the action fires at startup, in registration order, before any key is pressed: - A save writes a file the user never asked to save. - A quit action may tear the program down during initialisation. - An action that only computes something produces no visible effect at all, which is why this mistake often survives review - the damage is silent and shows up later. Then the key is pressed, the dispatcher retrieves the entry, and tries to apply it. What it holds is a plain result. Languages differ in what happens next: some reject the program before it runs because the entry's type is not callable, some raise a run-time failure at the press, and a few will coerce or silently do nothing. The one thing that never happens is the action running again, because there is no function in the entry to run. ## The dispatch site pays for the fix Once the entry holds behaviour, the press path grows one step: 1. Look the pressed key up in the table. 2. Decide what a missing entry means - ignore it, beep, or report an unbound key. 3. Apply the value that was found. This is where the parentheses belong, and it is the only place the body runs. That separation is the whole benefit. Registration decides **what** behaviour a key carries; dispatch decides **when** it happens. Collapsing them into one line by writing the call at registration destroys the distinction and, with it, the table. ## Reading the mistake in review A few habits catch it cheaply: - Read every assignment into a registry as a question: does the right-hand side name behaviour, or produce a result? - Be suspicious of any effect that fires during construction of a lookup table, a menu or a command map. - If the action needs arguments or extra steps, wrap it in a literal rather than calling it - `function() save() end`, not `save()`. - In a statically typed setting, declaring the table's element type as a function type turns the mistake into a compile error at the exact line that made it, which is the cheapest possible feedback. ## The general rule The rule generalises far past tables: anywhere a program stores, passes or returns behaviour, a bare name hands over the function and a name with parentheses hands over the outcome of one past call. A candidate who can state that difference in one sentence, point at the line that violates it and say where the call moved to has the model an interviewer is checking for. A candidate who calls the parentheses optional does not.

  • Once entries hold function values, what does the key-press site have to do that it did not do before?
    Two steps instead of one: look the key up, then apply what was found. It also has to decide what a missing entry means, because the lookup now returns behaviour to run rather than a result already computed for it.
  • Why does the same mistake often go unnoticed when the called function returns a value instead of writing to disk?
    Nothing visible happens at construction time, so the registration looks fine. The entry quietly holds a plain result, and the failure only surfaces later when the dispatcher tries to apply something that is not callable - far from the line that caused it.

A recipe card and a cooked meal are not interchangeable. Hand someone the card and they can cook it whenever they like; hand them the meal and the cooking already happened, once.

saying these in an interview costs you the question

  • Treats save and save() as interchangeable ways to name the function
  • Says storing a function value runs its body immediately
  • Thinks a table entry re-evaluates itself on every lookup
  • Says the fix is to build the table later during startup
  • Claims only dynamically typed languages can hold behaviour in a map