skip to content

questions

19

What is a borrow in an ownership discipline, and why can it never outlive the value's owner?

level: middleimportance: must knowfreq 66%

answer

  1. access without the release duty
  2. somebody else frees it, not you
  3. no counter, no life extension
  4. a window inside the owner's lifetime
  5. region checked before the program runs

basics

~20 s

A borrow is temporary non-owning access to a value someone else must release. It carries no release duty, so it stays valid only while that owner still holds the value; outliving the owner leaves it naming storage already reclaimed.

solid answer

~50 s

Ownership names exactly one place responsible for releasing a value. A **borrow** hands out access without transferring that duty: the borrower may read it, and with an exclusive borrow write it, but never releases it. Because release is still decided by the owner — its scope ending, or the owning value itself being destroyed — a borrow is only meaningful inside a window that the owner's lifetime contains. A compile-time discipline turns that containment into a checked property: every borrow carries a region of the program in which it may be used, and the checker rejects any use that could run after the owner is gone. That is why returning a reference into a value created inside a function is refused at compile time instead of becoming a dangling pointer later: the borrow's region would have to outlast the frame that owns the value.

go deeper

for a junior

Recall the two roles: one place owns the value and releases it, everyone else only borrows. A borrowed reference is not yours to free and not yours to keep.

for a middle

Explain the window: a borrow may only be used inside a region contained in the owner's lifetime, and a returned reference into a local breaks that containment. Say what the runtime symptom would have been.

for a senior

Show that you know the failure is load-dependent and hides in tests, and that you restructure code so the borrow's region is visibly narrower rather than arguing with the checker.

for a principal

Frame it as where enforcement sits: compile-time proof, runtime bookkeeping, or author discipline. Each moves the cost somewhere — build-time friction, per-access cost, or incident rate — and that choice belongs in the platform decision, not in each team.

## Owner, duty, borrow An ownership discipline answers one question for every value in the program: **who is obliged to release it?** The answer is a single place — a variable in a scope, a field of a larger value, a slot in a container. When that place goes away, the value it owns is released, exactly once. A **borrow** is permission to reach the value without taking that obligation. Other names for the same idea are a reference, a view, or non-owning access. Three properties define it: - **It does not release.** When a borrow stops being used, nothing at all happens to the value. - **It does not extend life.** The owner's release point is unchanged by how many borrows are outstanding; there is no bookkeeping that counts them. - **It is bounded.** Every borrow carries a *window* — a region of the program in which it may legally be used — and that window must sit inside the owner's own lifetime. | | The owner | A borrow | |---|---|---| | Releases the value? | Yes, exactly once | Never | | Keeps the value alive? | Yes, for its own lifetime | No | | May be duplicated? | No — a second owner means a move or a full copy | A read borrow may be duplicated freely | | Bounded by | Its own scope | A window contained in the owner's lifetime | ## Why the bound is containment, not politeness Release is scheduled by structure, not by demand. It happens when the owning scope exits — on the normal path, on an early return, and while an error is propagating — or when the owning value is itself destroyed. Nothing in that sequence consults outstanding references. So a use after the release point reads storage the allocator has taken back. What the program sees is undefined in the practical sense: the old bytes may still be there, the block may already hold a different value, or the pages may be gone. The failure is therefore **intermittent and load-dependent** — it hides in tests and appears when the workload changes. This is what the lifetime bound exists to make impossible. ## What a compile-time discipline actually proves 1. Each borrow is associated with a region of code, inferred from where it is created and where it is last used. 2. The checker compares that region against the lifetime of the thing borrowed from, and rejects the program if the region is not contained in it. 3. When a function returns a reference derived from a reference it was given, the result's region is tied to the input's, so the caller's own value is what keeps it valid. Two consequences matter in an interview. First, the check is **static**: there is no counter, no runtime test, and no cost at execution time — the cost is paid in expressiveness. Second, the check is **conservative**: it rejects some programs that would in fact have been safe, because it reasons about regions rather than about the exact execution. "The compiler rejected it" is not by itself proof that the code was wrong; it is proof that the code was not obviously right. ## Where this bites in real code - **Returning a reference into a locally created value.** The frame that owns it disappears at return, so the region cannot be contained in it. - **Storing a reference in a structure that outlives the source.** A cache entry, a registry, a handler held for later — the stored borrow's window is effectively unbounded. - **Holding a reference into a container across an operation that regrows it.** The owner is still alive, but the storage the reference names is not the container's storage any more. Ecosystems genuinely differ in where this is enforced. Some prove containment at compile time and refuse to build the program. Some leave it entirely to the author, so the same mistake becomes a runtime fault that may or may not reproduce. Some avoid the question by making every reference an owning one, paying for it with runtime bookkeeping or with a reclamation pass. The **rule** — access must not outlive what it accesses — is the same in all three; only the moment of enforcement moves. ## What to say when asked - Name the duty: one owner releases; a borrow never does. - State the invariant in one line: the borrow's window must be contained in the owner's lifetime. - Give the canonical rejection — a reference into a local, returned — and say what it would have become at runtime. - Close with the trade: static checking buys a whole bug class for zero runtime cost and some rejected-but-safe programs.

  • Does taking a borrow keep the borrowed value alive?
    No. A borrow records permission to look, not a claim on the value's life. The owner's scope still decides the release point, and the discipline's job is to prove that no borrow is usable after it. Handles that genuinely extend a value's life are a different mechanism, with runtime bookkeeping attached.
  • If a function cannot return a reference into its own frame, how can it return a reference at all?
    By returning one derived from a reference it was given. The result's window is tied to that input, so the value the caller already owns is what keeps the result valid. Anything the function created locally is released at return and cannot escape this way.
  • The checker rejects code you are sure is safe. What does that tell you?
    That the safety depends on facts the checker does not model — an ordering it cannot see, or a region that is wider than the real use. The honest responses are to restructure so the containment is visible, narrow the borrow, or take an owned copy. Assuming the checker is simply wrong is how the bug gets reintroduced elsewhere.

Borrowing a book from a colleague's desk: you may read it, you never decide when it is thrown away, and you must be done before the colleague clears the desk.

saying these in an interview costs you the question

  • Thinks a borrow shares the release duty with the owner
  • Says outstanding borrows keep the value alive
  • Believes returning a reference to a local is fine if the caller reads it quickly
  • Assumes passing by reference makes a defensive copy
  • Treats the lifetime bound as a runtime check rather than a static proof
open as a page

Why may many readers borrow the same value at once, while a writer must borrow it exclusively?

level: middleimportance: must knowfreq 58%

basics

~20 s

Readers all observe one unchanging value, so overlapping them changes nothing. A writer can resize, move or half-update it, so any other reference alive at that moment could read a torn or stale view. Exclusivity for writers is what rules that out.

open as a page

Why can a program built under a compile-time ownership discipline still leak heap memory with no unchecked code?

level: middleimportance: must knowfreq 58%

basics

~20 s

A leak is memory that is still owned and still reachable — a cache that never evicts, a registration nobody cancels. The checks prove every access is valid; they say nothing about how long an owner chooses to keep a block.

open as a page

Which memory-bug classes does a compile-time ownership discipline make unrepresentable rather than merely rarer?

level: middleimportance: must knowfreq 66%

basics

~20 s

Use-after-free, double free, and dangling references into a moved or resized container become unrepresentable: single ownership plus lifetime-checked borrows reject them before the program builds. Leaks and logic errors are untouched, because keeping memory reachable is safe.

open as a page

In a pipeline where one owning handle to a large chunk buffer carries the release duty, what does moving that handle do?

level: middleimportance: must knowfreq 66%

basics

~20 s

A move transfers the release duty to the destination handle and leaves the source owning nothing. The block stays at the same address; only the small owning record is copied, so the cost is the same for 4 KiB and 64 MiB.

open as a page

How does tying release to scope exit free a lock, a temporary file and a connection when a job returns early?

level: middleimportance: must knowfreq 65%

basics

~20 s

Each resource is wrapped in a guard object that acquires in its constructor and releases in its destructor. The scope owns those objects, so the compiler emits teardown on every edge out of the block: normal return, early return and error unwinding alike.

open as a page

A pipeline stage moves its chunk buffer onward and then reads the length from the old handle — what is wrong?

level: seniorimportance: must knowfreq 54%

basics

~20 s

That is a use-after-move: the old handle no longer owns the buffer it is being asked about. Disciplines that prove ownership statically reject the read outright; where it compiles, it returns the moved-from empty value or worse, which is a silent data bug.

open as a page

When a scope exits because an error is unwinding, what must a guard's release do differently from a normal return?

level: seniorimportance: must knowfreq 46%

basics

~20 s

It must assume the work was abandoned, not finished: a transaction guard rolls back, a temporary-file guard deletes, a partial output is discarded. It must also not raise a failure of its own, because a second failure during unwinding has nowhere to go.

open as a page

When a pipeline stage hands a 32 MiB chunk buffer onward, how do moving it and deep-copying it differ in cost?

level: middleimportance: should knowfreq 47%

basics

~20 s

Moving costs a few word stores and no allocation, whatever the chunk size. A deep copy costs one allocation plus 32 MiB of memory traffic per handoff, and leaves two blocks live and two releases to run.

open as a page

Why does a scope release its resources in the reverse of the order in which they were acquired?

level: middleimportance: should knowfreq 48%

basics

~20 s

Later acquisitions may be built on earlier ones, so reverse order guarantees nothing is torn down while something that uses it is still alive. Acquisition nests, and last-in-first-out teardown is the only order that respects that nesting for every dependency.

open as a page

A routine holds a reference to one row of an in-memory table, appends more rows, then reads through that reference. What can go wrong?

level: seniorimportance: should knowfreq 52%

basics

~20 s

If the table keeps its rows in one contiguous block, an append past its capacity allocates a larger block, copies the rows and releases the old one. Any reference taken earlier now names released storage, so the later read is meaningless.

open as a page

A handler is stored for later that captures a reference to a row created inside the registering function. Why is that rejected?

level: seniorimportance: should knowfreq 38%

basics

~20 s

Storing the handler gives the captured reference an unbounded window: it may run at any later point, including after the registering function has returned and released the row. A borrow may only be used inside a window contained in the owner's lifetime.

open as a page

What is a compile-time ownership guarantee worth when a few marked regions suspend the checks?

level: seniorimportance: should knowfreq 38%

basics

~20 s

It is worth containment. The eliminated classes can only be reintroduced inside a small, explicitly marked surface, so review and testing concentrate there and the rest of the codebase needs no such reasoning. The proof is conditional, not void.

open as a page

What does a pipeline stage that takes the chunk buffer's owning handle by value promise its caller about that buffer?

level: seniorimportance: should knowfreq 41%

basics

~20 s

Taking the owning handle by value says the stage now carries the release duty: the caller's handle is spent and the stage, not the caller, decides the buffer's fate. Returning a handle back says the duty comes home.

open as a page

Release at scope exit reports no failure, so for which resources is that unacceptable, and what would you mandate instead?

level: principalimportance: should knowfreq 32%

basics

~20 s

Silent teardown is fine where release is a local give-back: locks, handles, memory, pooled connections. Anything whose completion can fail and changes what the system believes needs an explicit checked call, with the guard as the rollback backstop.

open as a page

A guard holding an export lock is declared at the top of a long function. What does that cost, and how would you fix it?

level: seniorimportance: nice to knowfreq 28%

basics

~20 s

The lock is held for the whole function, including slow work it does not protect, so unrelated callers queue behind a network round trip. Size the scope to the critical section: put the guard in an inner block.

open as a page

You are setting the accessor policy for an in-memory dataset library: when should a call hand back a borrowed view rather than an owned copy?

level: principalimportance: nice to knowfreq 26%

basics

~20 s

Return a view only for large results read immediately inside your own build, where copying would dominate. Default to an owned copy otherwise: a view bans mutation of the dataset for as long as any caller holds it.

open as a page

How would you judge whether rewriting a binary-protocol parser under a compile-time ownership discipline will actually close its defect tracker?

level: principalimportance: nice to knowfreq 28%

basics

~20 s

Classify the open defects first. The rewrite closes use-after-free, double free and dangling-reference rows by construction and nothing else; logic, retention and error-handling rows survive it. The decision is that class mix against the migration cost.

open as a page

You set the stage contract for an upload pipeline used by many teams: when is deep-copying each chunk buffer the honest choice over moving it?

level: principalimportance: nice to knowfreq 28%

basics

~20 s

Move by default, so footprint tracks in-flight chunks rather than pipeline depth. Allow a copy only where a stage must retry from the original bytes, must keep them for a different lifetime, or where the payload is small enough that the copy is noise.

open as a page