The classic method for everything-but-last is "init". Whether this is constant-time in the case of ArrayBuffer is a different question...
In 2.9.2, ArrayBuffer.init comes from IndexedSeqOptimized:
def init: Repr = if (length > 0) slice(0, length - 1) else super.init
It looks like ArrayBuffer's slice is also inherited from IndexedSeqOptimized:
def slice(from: Int, until: Int): Repr = {
val lo = math.max(from, 0)
val hi = math.min(until, length)
val elems = math.max(hi - lo, 0)
val b = newBuilder
b.sizeHint(elems)
var i = lo
while (i < hi) {
b += self(i)
i += 1
}
b.result
}
Sadly, init is linear in 2.9. I just checked 2.10, and it looks the same there:
Now, remove claims that it's linear time, but a lot of the work is done by System.arraycopy, which is really, really fast, and the rest of the work is in reduceToSize, which is linear, but your n is 1.