What is a Java NIO Buffer, and what do its four state markers (capacity, limit, position, mark) mean?
answer
- 0 <= mark <= position <= limit <= capacity
- position = next slot, limit = the wall you can't cross
- capacity is fixed at allocation; never changes
- mark is a bookmark; reset() returns to it
- remaining() = limit - position
basics
~20 sA Buffer is a fixed-size box that holds one kind of primitive value (like bytes). It tracks where you are with four numbers: capacity (total size), limit (how far you can go), position (the next slot to read/write), and mark (a saved spot you can jump back to).
solid answer
~40 sA java.nio.Buffer is a fixed-capacity container for a single primitive type (ByteBuffer, IntBuffer, etc.). Its cursor state is four ints obeying 0 <= mark <= position <= limit <= capacity. Capacity is the total number of elements, fixed at allocation. Position is the index of the next element to be read or written; it advances on every get/put. Limit is the first index you may NOT touch — the boundary of accessible data. Mark is an optional remembered position you set with mark() and return to with reset(). The whole API works because reads and writes both move position toward limit, so the same object serves both phases as long as you reset limit and position correctly between them. Operations like flip(), clear(), and rewind() just reposition these markers.
go deeper
Can name all four markers and state that capacity is fixed and position is the next slot to read/write.
States the full invariant 0 <= mark <= position <= limit <= capacity and explains why both position and limit are needed for the fill-then-drain pattern.
Connects the markers to flip/clear/rewind/compact, knows remaining()/hasRemaining(), and the exception behavior at boundaries.
Reasons about the model's design trade-offs — single mutable cursor object, no thread safety, why fixed capacity simplifies native-memory mapping, and how this informs API design for zero-copy I/O.
## What a Buffer is In classic Java I/O you read and write through *streams* — you hand bytes to an `OutputStream` or pull them from an `InputStream`, one direction at a time, with the stream managing its own internal buffering invisibly. Java NIO (New I/O, package `java.nio`, since Java 1.4) inverts this: you work with an explicit, in-memory container called a **Buffer**, and channels move data between that buffer and the outside world (a file, a socket). A **Buffer** is a **fixed-size container for a single primitive type**. "Fixed-size" means once you allocate it, its total capacity never changes. "Single primitive type" means there is a separate subclass per primitive: `ByteBuffer`, `CharBuffer`, `IntBuffer`, `LongBuffer`, `FloatBuffer`, `DoubleBuffer`, `ShortBuffer` (and `MappedByteBuffer`). `ByteBuffer` is the one you use most, because channels read and write bytes. ## The four markers A buffer is conceptually an array plus four integer cursors. They always satisfy this invariant: ``` 0 <= mark <= position <= limit <= capacity ``` Let me define each, from the outside in: - **capacity** — the total number of elements the buffer can hold. Set once at allocation (`ByteBuffer.allocate(64)` → capacity 64) and **never changes**. It is the hard ceiling for every other marker. - **limit** — the index of the *first element you are NOT allowed to read or write*. Everything from `position` up to (but not including) `limit` is the accessible region. Initially `limit == capacity` (you may use the whole buffer). - **position** — the index of the *next* element a `get()` or `put()` will touch. It starts at 0 and **advances by one each time you read or write one element** (or by N for a bulk operation). When `position == limit`, there is nothing left in the current region. - **mark** — an *optional* bookmark. It is undefined ("unset") until you call `mark()`, which records the current `position`. Later, `reset()` sets `position` back to that marked value. The mark is discarded (becomes undefined) if `position` or `limit` is ever moved below it. ## Why limit and position both exist The genius (and the trap) of the model is that **the same buffer is used for both writing and reading**, and the markers tell the buffer which phase it is in. - While **filling** the buffer (e.g. `channel.read(buf)` writes data into it), `position` marks how much you have written and `limit` stays at capacity. - Then you **flip** with `flip()`: it sets `limit = position` ("the data ends here") and `position = 0` ("start reading from the front"). Now the accessible region is exactly the data you just wrote. - While **draining** the buffer (e.g. `channel.write(buf)` reads data out of it), `position` advances through the data until it hits `limit`. - `clear()` resets to `position = 0, limit = capacity` (ready to fill again — it does NOT erase the bytes). `rewind()` sets `position = 0` but leaves `limit` (re-read the same data). `compact()` moves unread bytes to the front and positions you to keep filling. ## remaining() and hasRemaining() `remaining()` returns `limit - position` — the number of elements still accessible. `hasRemaining()` is `position < limit`. These are how you loop over a buffer without ever touching the markers by hand. ## Edge cases and rules - Setting `limit` below the current `position` *also* drops `position` down to the new limit. Setting `position` (or `limit`) below `mark` discards the mark. - `reset()` throws `InvalidMarkException` if no mark is set. - Reading past `limit` (a relative `get()` when `!hasRemaining()`) throws `BufferUnderflowException`; writing past it throws `BufferOverflowException`. Absolute `get(index)`/`put(index, v)` ignore `position`/`limit` but still bounds-check against capacity (throwing `IndexOutOfBoundsException`). - Buffers are **not thread-safe**; the markers are plain mutable state. Once you internalize the invariant `0 <= mark <= position <= limit <= capacity` and the rule "position is the next slot, limit is the wall," every buffer method becomes predictable.
- What does remaining() return, and in terms of which markers?It returns limit - position: the number of elements between the current position and the limit, i.e. how many you can still read or write.
- What happens to the mark if you set the limit below it?The mark is discarded (becomes undefined). Any later reset() before re-marking throws InvalidMarkException.
saying these in an interview costs you the question
- Thinking capacity can grow — buffers are fixed-size
- Confusing limit with capacity (limit moves during read phase; capacity never does)
- Believing clear() zeroes the data (it only resets markers)
- Saying position points at the last element used (it points at the NEXT slot)
- Assuming a mark always exists (it is undefined until mark() is called)