Part of Github Copilot with Kotlin

Kotlin: Remove last items from list and array

Sandy LaneSandy Lane

Video: Kotlin: Remove last items from list and array by Taught by Celeste AI - AI Coding Coach

Take the quiz on the full lesson page
Test what you've read · interactive walkthrough

Kotlin: Remove the Last Items from a List and Array

mutableList.removeAt(mutableList.lastIndex) mutates in place. list.dropLast(n) returns a new list. Arrays need .copyOfRange since they're fixed-size.

A small puzzle that exposes the mutable/immutable split in Kotlin's collection API. Different operations for MutableList, List, and Array.

The Copilot prompt

fun main() {
  val mutableList = mutableListOf("apple", "banana", "cherry", "date")
  // Remove the last item

Copilot generates:

if (mutableList.isNotEmpty()) {
  mutableList.removeAt(mutableList.size - 1)   // removes "date"
}
println(mutableList)   // [apple, banana, cherry]

removeAt for MutableList

mutableList.removeAt(index) mutates the receiver. Returns the removed element.

mutableList.size - 1 is the last valid index. Kotlin offers a clearer property:

mutableList.removeAt(mutableList.lastIndex)

lastIndex is size - 1 for non-empty lists. Cleaner than the manual subtraction.

For Kotlin 1.4+:

mutableList.removeLast()

removeLast() is the dedicated method for "remove the last element." Throws NoSuchElementException on empty. There's also removeLastOrNull() (returns nullable, no throw).

Removing N from the end

val n = 2
repeat(n) {
  if (mutableList.isNotEmpty()) {
    mutableList.removeLast()
  }
}

Loop n times. Or do it in one operation:

val safe = mutableList.dropLast(n)   // returns NEW list, original unchanged

dropLast(n) returns a new immutable list with the last n elements removed. Negative or zero n returns the list unchanged. Larger n than the list size returns an empty list.

Note the difference: removeLast() mutates; dropLast(n) creates a copy.

On immutable List

val immutable = listOf("apple", "banana", "cherry", "date")
val without_last = immutable.dropLast(1)   // ["apple", "banana", "cherry"]
val without_last_three = immutable.dropLast(3)   // ["apple"]

List (non-mutable) doesn't have removeAt or removeLast. Use dropLast(n) to produce a new list.

On Array

val array = arrayOf("apple", "banana", "cherry", "date")

// Arrays are fixed-size — can't shrink in place
val truncated = array.copyOfRange(0, array.size - 1)
println(truncated.joinToString())   // apple, banana, cherry

Array<T> in Kotlin has a fixed length; you can't add or remove. To "remove" an element, build a new array with copyOfRange, copyOf, or filter + toTypedArray:

val withoutLast = array.copyOf(array.size - 1)        // []  // truncate
val withoutFirst = array.copyOfRange(1, array.size)  // skip first
val filtered = array.filter { it != "date" }.toTypedArray()

For mutable resizable behaviour, MutableList is the right choice. Array is for fixed-size, primitive-friendly storage.

Removing while iterating (gotcha)

for (item in mutableList) {
  if (someCondition) {
    mutableList.remove(item)   // ConcurrentModificationException!
  }
}

Modifying a list while iterating it with for throws. Three safe patterns:

// 1. Iterator with explicit remove
val it = mutableList.iterator()
while (it.hasNext()) {
  if (someCondition(it.next())) {
    it.remove()
  }
}

// 2. removeAll with a predicate (Kotlin 1.0)
mutableList.removeAll { someCondition(it) }

// 3. Build a new list
val filtered = mutableList.filter { !someCondition(it) }

removeAll(predicate) is the cleanest for "remove all matching."

Edge cases

  • Empty list. removeLast() throws; removeLastOrNull() returns null; dropLast(1) returns the empty list.
  • dropLast(0). Returns the original list unchanged.
  • dropLast(huge). If huge > size, returns empty list. No error.
  • removeAt(size). Out-of-bounds; throws IndexOutOfBoundsException.

When to mutate vs when to copy

  • Mutate when you have a MutableList and you own it. Cheap, no allocation.
  • Copy (dropLast, filter, etc.) when you have an immutable List, or when you want to preserve the original (e.g., for testing, undo, or thread-safety).

The functional style (copy) is more verbose but easier to reason about — pure functions, no shared mutation.

Common mistakes

Calling removeAt(size) instead of lastIndex. Off-by-one; throws.

Mutating a listOf(). listOf(...) returns List<T>, not MutableList<T>. add/remove aren't available. Use mutableListOf(...) for a mutable list.

Trying to removeLast() from an Array. Doesn't exist. Arrays are fixed-size. Use copyOf(size - 1).

Forgetting the repeat(n) { ... } is required for "remove last N" on mutable. Single removeLast() removes one. To remove n, loop or use dropLast(n) (returns new list).

Mutating during a for-loop. ConcurrentModificationException. Use removeAll(predicate) or build a new list.

What's next

Episode 18: Extension function — is this Int prime? Add a method to a built-in type that you don't own.

Recap

mutableList.removeLast() (Kotlin 1.4+) for in-place last-element removal. mutableList.removeAt(index) for any index. list.dropLast(n) for an immutable copy with the last n removed. Array<T> is fixed-size — use array.copyOf(newSize) or copyOfRange to "shrink." removeAll(predicate) is the safe way to filter-mutate without iterator surprises.

Next episode: extension function for prime check.

Ready? Take the quiz on the full lesson page →
Test what you've learned. Watch the lesson and try the interactive quiz on the same page.