After writing data into a ByteBuffer, what must you do before reading it back, and why?
answer
- One position cursor shared by read and write
- flip(): limit = position, position = 0
- Fill, flip, drain
- Skipping flip reads garbage up to capacity
- clear() before writing, flip() before reading
basics
~20 sCall flip() before reading. Writing moves the position forward; flip() sets the limit to where you stopped writing and resets position to 0, so reads start at the beginning and stop where the data ends.
solid answer
~40 sA NIO Buffer has one position cursor shared by reads and writes, plus a limit. While you write with put(), position advances. If you then call get() without flipping, you read garbage past your data and stop at capacity, not at your data's end. flip() does limit = position; position = 0. That makes position mark the start of valid data and limit mark its end, so subsequent get() calls walk exactly over what you wrote and hasRemaining() correctly reports when you are done. The canonical pipeline is: clear() (or allocate), channel.read()/put() to fill, flip(), then channel.write()/get() to drain. Forgetting flip() is the single most common NIO bug; symptoms are reading zeros or reading more than you wrote.
go deeper
Knows the rule: flip() before reading what you just wrote, clear() before writing again. Can state flip sets position to 0.
Explains the single shared position/limit model and exactly what flip() assigns (limit=position, position=0), and why skipping it reads past the data.
Articulates the full fill/flip/drain and the clear-vs-compact choice for partial drains; recognizes the double-flip bug and contrasts flip with rewind and compact.
Frames buffer-mode discipline as an API design trade-off (one cursor, manual mode switching) and can reason about it in zero-copy/channel pipelines, partial reads, and how mishandling causes subtle data-corruption bugs at scale.
## What a Buffer is In Java NIO (`java.nio`), a **Buffer** (e.g. `ByteBuffer`, `CharBuffer`) is a fixed-size container for primitive data backed by an array or off-heap memory. Unlike a plain array, a Buffer tracks state with three integer markers, always satisfying `0 <= mark <= position <= limit <= capacity`: - **capacity** — the total fixed size; set at allocation, never changes. - **position** — the index of the *next* element to be read or written. This single cursor is shared by both reading and writing. - **limit** — the index of the first element that should *not* be read or written; the boundary of the active region. - **mark** — a remembered position you can return to (optional). ## Why a single shared cursor causes the problem Because one `position` serves both modes, the buffer does not know whether you are currently filling it or draining it — *you* must tell it by adjusting the markers. When you allocate a buffer, `position = 0` and `limit = capacity`, i.e. it is in **write mode**: there is room from 0 up to capacity. Each `put(b)` (relative write) stores at `position` then does `position++`. So after writing N bytes, `position == N` and `limit` is still `capacity`. Now suppose you call `get()` (relative read) directly. `get()` reads at `position` then does `position++`. But `position` is N — past your data — and it will keep reading up to `limit == capacity`. You read leftover/zero bytes and far too many of them. This is the classic NIO failure. ## What flip() does `flip()` is exactly: ``` limit = position; // mark the END of the data you wrote position = 0; // go back to the START mark = -1; // discard any mark ``` Now `position = 0` (read from the beginning) and `limit = N` (stop at the end of valid data). The buffer is in **read mode**. Relative `get()` calls walk from 0 to N-1, and `hasRemaining()` (`position < limit`) becomes false exactly when you have read all N bytes. ## The canonical write-then-read pipeline ``` buf.clear(); // write mode: position=0, limit=capacity int n = channel.read(buf); // fills the buffer; advances position buf.flip(); // switch to read mode: limit=position, position=0 while (buf.hasRemaining()) { // drain process(buf.get()); } ``` Think of it as: **fill, flip, drain**. Every time you switch from putting data in to taking data out, you flip; every time you switch back to filling, you `clear()` (or `compact()` if unread data remains). ## Edge cases - Calling `flip()` twice in a row sets `limit = 0` (since position is now 0), making the buffer empty for reading — a real bug. - `flip()` does not erase data; it only repositions the markers. - Absolute `get(index)`/`put(index, b)` ignore and do not change `position`, so they are not affected by mode, but the common relative methods are.
- What happens if you call flip() twice in a row?The first flip sets limit=position and position=0. The second flip sets limit=position=0, leaving an empty read region (nothing remaining) — a common bug that makes it look like there is no data.
- What is the difference between flip() and rewind() before reading?flip() sets limit=position then position=0 — used after writing to bound reads to the data just written. rewind() only sets position=0 and leaves limit unchanged — used to re-read data already bounded by a prior flip().
saying these in an interview costs you the question
- Thinking flip() erases or clears the data (it only moves markers)
- Calling flip() twice and ending with limit=0
- Believing reads automatically start at index 0 without flip()
- Confusing flip() with rewind() (flip also sets limit)