skip to content

How do mark() and reset() work on a Buffer, and what edge cases govern the mark?

level: middleimportance: should knowfreq 40%

answer

  1. mark() saves position; reset() sets position = mark
  2. mark is undefined until mark() is called
  3. moving position/limit below mark discards it
  4. flip/clear/rewind/compact all wipe the mark
  5. reset() with no mark -> InvalidMarkException (unchecked)

basics

~20 s

mark() saves the current position so you can come back to it. reset() moves position back to that saved spot. If you call reset() without ever calling mark(), or after the mark was wiped out, you get an InvalidMarkException.

solid answer

~40 s

mark() records the current position into the buffer's mark field; reset() sets position back to that recorded value, letting you re-read or re-process a region. The mark is undefined until you call mark(). It gets silently discarded whenever position or limit is moved below it (so flip(), clear(), rewind(), and compact() all clear it), and also if you explicitly set position/limit to a value less than the mark. Calling reset() with no valid mark throws InvalidMarkException — a RuntimeException, so it is unchecked. The invariant the mark participates in is 0 <= mark <= position <= limit <= capacity, which is exactly why moving position or limit below mark invalidates it: keeping it would violate the ordering.

go deeper

for a junior

Knows mark() saves a spot and reset() returns to it.

for a middle

Explains that the mark is optional, can be discarded by marker-moving ops, and that reset() without a mark throws InvalidMarkException.

for a senior

Ties the mark's lifecycle to the full invariant, lists exactly which methods discard it, and notes the exception is unchecked.

for a principal

Discusses the mark as a minimal one-level backtrack primitive, its limits versus duplicate()/slice() for non-destructive views, and design implications for parser/codec code.

## The mark is a bookmark Among the four buffer markers, **mark** is the only *optional* one. Capacity, limit, and position always have meaningful values; the mark starts **undefined** and only becomes defined when you ask for it. - **`mark()`** copies the current `position` into the internal `mark` field and returns the buffer (so it chains). Think of it as dropping a bookmark at "where I am right now." - **`reset()`** sets `position = mark`, moving you back to the bookmark. It does **not** clear the mark, so you can `reset()` repeatedly to the same spot. A typical use: you are parsing a buffer, you `mark()` before trying to read a token, and if the token turns out to be incomplete or the wrong type, you `reset()` to retry from where you started — a cheap one-level rewind/backtrack. ## The governing invariant The four markers must always satisfy: ``` 0 <= mark <= position <= limit <= capacity ``` The mark sits at the bottom of this chain (when defined). This single rule explains every edge case: - If you move **position** to a value below the current mark, keeping the mark would break `mark <= position`. So the mark is **discarded** (reset to undefined). - Likewise, if you move **limit** below the mark, `mark <= limit` would break, so again the mark is **discarded**. ## Which operations wipe the mark Because of the above, the mark is silently cleared by any operation that pulls position or limit down past it. In practice that means: - **`flip()`** — sets position to 0 (and limit down), discards mark. - **`clear()`** — position to 0, discards mark. - **`rewind()`** — position to 0, discards mark. - **`compact()`** — repositions, discards mark. - **`position(int)` / `limit(int)`** set below the mark — discard it. So in practice a mark is short-lived: set it, use it, before any of the phase-shifting methods run. ## reset() with no mark If you call `reset()` when the mark is undefined (never set, or already discarded), the buffer throws **`InvalidMarkException`**. This is an **unchecked** exception (it extends `IllegalStateException` → `RuntimeException`), so the compiler does not force you to handle it — you must defensively ensure a mark was set, or be sure no marker-moving method ran in between. ## Quick contrast with rewind() `rewind()` always goes to position 0 and discards the mark; `reset()` goes to the *marked* position and requires a mark. Use `rewind()` to restart the whole region, `mark()/reset()` to bookmark and return to an arbitrary point.

  • What exception does reset() throw if no mark is set, and is it checked?
    InvalidMarkException, which extends IllegalStateException (a RuntimeException), so it is unchecked — the compiler does not force a try/catch.
  • Why does setting the limit below the mark discard the mark?
    Because the invariant 0 <= mark <= position <= limit <= capacity must hold; a mark above the new limit would violate mark <= limit, so it is invalidated.

saying these in an interview costs you the question

  • Assuming a mark always exists and that reset() is always safe
  • Thinking reset() clears the mark (it keeps it; you can reset repeatedly)
  • Confusing reset() (go to mark) with rewind() (go to 0)
  • Believing InvalidMarkException is checked (it is a RuntimeException)
  • Expecting the mark to survive flip()/clear()

context