skip to content

Explain flip(), clear(), rewind(), and compact() in terms of how each moves the Buffer's position, limit, and mark.

level: middleimportance: must knowfreq 65%

answer

  1. flip: limit=position, position=0 (fill->drain)
  2. clear: position=0, limit=capacity (does NOT erase)
  3. rewind: position=0, limit unchanged (re-read)
  4. compact: slide unread to front, position=#unread, limit=capacity
  5. canonical loop: read; flip; write; compact

basics

~20 s

flip() gets a buffer ready to read what you just wrote: it sets limit to the current position, then position to 0. clear() gets it ready to write again: position 0, limit at capacity (data is left untouched). rewind() sets position to 0 to re-read. compact() keeps the unread bytes and lets you keep writing.

solid answer

~40 s

These methods just reposition the markers. flip(): limit = position; position = 0; mark discarded — switches from fill mode to drain mode so you read exactly what was written. clear(): position = 0; limit = capacity; mark discarded — switches back to fill mode; it does NOT erase data, just makes the whole buffer writable again. rewind(): position = 0; limit unchanged; mark discarded — re-read the same region from the start. compact(): copies the unread bytes (position..limit) to the front, sets position to the number of bytes copied and limit = capacity — useful when you have partially drained a buffer and want to top it up with more data without losing the leftover. The classic read loop is: while(channel.read(buf) >= 0){ buf.flip(); consume(buf); buf.compact(); }.

go deeper

for a junior

Knows flip() is needed before reading what was written and clear() before refilling.

for a middle

Can state the exact marker assignments each method makes and explain flip vs clear vs rewind vs compact.

for a senior

Writes the correct read/copy loop, chooses compact() over clear() for partial drains, and explains that no method erases data.

for a principal

Discusses the API's design (cursor-shifting vs copying), performance of compact()'s memmove, and how the fill/drain duality enables reuse and zero-copy patterns.

## The two phases of a buffer A NIO buffer cycles between two roles, and these four methods are the gear-shifts between them: - **Fill (write into the buffer):** you put data in. `position` tracks how much you have put; `limit` is at `capacity` so you can use it all. - **Drain (read out of the buffer):** you take data out. `position` walks through the data; `limit` marks where the data ends. Remember the invariant: `0 <= mark <= position <= limit <= capacity`. `position` is the *next* slot; `limit` is the *wall*. ## flip() — fill → drain ``` limit = position; // "the data I wrote ends right here" position = 0; // "start reading from the front" mark discarded ``` After you fill a buffer, `position` sits just past the last byte written. `flip()` plants `limit` there (so a reader stops at the real end, not at capacity) and rewinds `position` to 0. The accessible region `[position, limit)` is now exactly the data you wrote. Forgetting `flip()` before reading is the single most common NIO bug: you end up reading from where you stopped writing, often getting zero bytes (`position == limit`). ## clear() — drain → fill (start over) ``` position = 0; limit = capacity; mark discarded ``` `clear()` makes the *whole* buffer writable again. Crucially it **does not zero the contents** — the old bytes are still physically present; you just overwrite them as you fill. "Clear" means "clear the markers," not "clear the memory." ## rewind() — re-read ``` position = 0; limit unchanged mark discarded ``` `rewind()` is like `flip()` but leaves `limit` alone. Use it when you have already drained a buffer once and want to read the *same* region again (e.g. write the same payload to two channels). ## compact() — drain partially, then keep filling ``` copy bytes [position, limit) down to index 0 position = number of bytes copied (limit - old position) limit = capacity mark discarded ``` `compact()` handles the common case where you read *some* of a buffer but not all of it, and now want to pour more data in without losing the unread leftover. It slides the remaining unread bytes to the front, then leaves `position` right after them so the next fill appends. This is why the canonical copy loop uses `compact()`, not `clear()` — `clear()` would discard any not-yet-consumed bytes. ## The canonical loop ```java ByteBuffer buf = ByteBuffer.allocate(1024); while (in.read(buf) != -1) { // fill buf.flip(); // switch to drain out.write(buf); // drain (maybe partial) buf.compact(); // keep leftovers, switch back to fill } buf.flip(); // drain the final remainder while (buf.hasRemaining()) out.write(buf); ``` ## Summary table | method | position | limit | erases data? | use when | |---|---|---|---|---| | flip() | 0 | old position | no | finished filling, about to read | | clear() | 0 | capacity | no | done with data, start filling fresh | | rewind() | 0 | unchanged | no | re-read the same region | | compact()| #unread bytes | capacity | no | partially drained, want to keep filling | None of them touch the actual element bytes; they only move cursors (compact additionally *copies* the unread region). Each discards the mark.

  • Why does the standard copy loop use compact() instead of clear()?
    A drain (channel.write) may be partial, leaving unread bytes in the buffer. clear() would discard them; compact() preserves the unread bytes by sliding them to the front, then lets you append more data.
  • Does clear() erase the buffer's contents?
    No. It only resets position to 0 and limit to capacity. The underlying bytes remain; they are simply overwritten as you refill.

saying these in an interview costs you the question

  • Believing clear() or flip() zeroes the buffer's bytes
  • Reading before calling flip() (you read garbage / zero bytes)
  • Using clear() instead of compact() in a copy loop and losing unread bytes
  • Thinking rewind() resets limit too (it does not)
  • Forgetting all four discard the mark

context