Overview

5 Data handling with lists

This chapter introduces data structures with an emphasis on lists, explaining how collections can be classified (linear, associative, and graph-based) and why lists are the canonical vehicle for learning core ideas. It contrasts access patterns and expected performance using Big O notation, highlighting the unavoidable trade-offs among operation speed, memory usage, and implementation complexity. A central theme is immutability: in-place mutation is discouraged—especially in concurrent contexts—while persistent data structures that share memory between versions offer safer and often surprisingly efficient behavior. Kotlin’s standard mutable and “immutable” lists are examined, motivating a truly immutable, persistent singly linked list tailored for efficient stack-like operations.

The chapter builds such a list in Kotlin using a sealed hierarchy with Nil (empty) and Cons (nonempty) nodes, modeling a recursive head/tail structure that enables data sharing. It develops essential operations—constructing with cons, replacing the head, dropping elements (by count or predicate), reversing, concatenating, and trimming from the end—while discussing stack safety, tail recursion, and the advantages of object notation versus companion/package-level functions. It also tackles type variance to make Nil reusable across element types (List as a universal empty), showing when and how to apply covariance (out A) and carefully use @UnsafeVariance for functions that accept A in input position.

Finally, the chapter elevates list processing through higher-order folds: foldRight, foldLeft, and a stack-safe coFoldRight, using them to derive sum, product, length, reverse, concat, and flatten. With folds as the core traversal abstraction, common transformations emerge naturally: map (apply a function to each element), filter (retain by predicate), and flatMap (map to lists then flatten), reinforcing composition over explicit recursion. The result is a cohesive toolkit for immutable list handling that is expressive, safer under concurrency, and efficient for many workloads thanks to structural sharing and well-chosen, stack-safe implementations.

Figure 5.1. Different types of lists offer different types of access to their elements
Fig05 01
Figure 5.2. Removing and adding elements without mutation or copying
Fig05 02
Figure 5.3. The representation of the singly linked list implementation
Fig05 03
Figure 5.4. Dropping the n first elements of a list while not mutating or creating anything.
Fig05 04
Figure 5.5. Without object notation (left), composed functions may be difficult to read. Using object notation (right) results in much more readable code
Fig05 05
Figure 5.6. Sharing data by concatenation. You can see that both lists are preserved and that list2 is shared by the resulting list. But you can also see that you can’t proceed exactly as is indicated in the figure, because you’d have to access the last element of list1 first, which is of course not possible due to the structure of the list.
Fig05 06

 Summary

  • Data structures are one of the most important concepts in programming because they allow handling multiple data as a whole.
  • The singly linked list is the very efficient data structure for programming with functions. It has the benefits of immutable lists while allowing some modifications, such as insertion and removal in first position, in constant (and very short) time because unlike Kotlin (immutable) lists, des don’t need to perform copy for this.
  • Using data sharing allows for very high performance for some operations, although not for all.
  • You can create other data structures to get good performance for specific use cases.
  • You can fold lists by recursively applying functions.
  • You can use corecursion to fold lists without the risk of overflowing the stack.
  • Once you’ve defined foldRight and foldLeft, you shouldn’t need to use (co)recursion again to handle lists. foldRight and foldLeft abstract (co)recursion for you.

In chapter 8, you’ll learn how to add many powerful function to the List class.

FAQ

How are data collections classified in this chapter, and where do lists fit?Data collections are categorized as linear (elements related along one dimension, e.g., lists), associative (membership determined by a predicate, e.g., sets and maps), and graph-based (elements relate to multiple others, e.g., trees). Lists are linear collections and can further be classified by access (single/both ends, indexed), ordering (FIFO/LIFO/custom), and implementation (linked vs array-backed, singly vs doubly linked).
Why prefer immutable, persistent lists over Kotlin’s standard "immutable" lists?Kotlin’s standard immutable lists rely on defensive copies, so many updates copy the whole structure and do not share data. A persistent list uses structural sharing: operations create new lists that reuse existing nodes, enabling efficient, thread-safe updates without mutation and avoiding the performance penalties of full copies.
What are the performance trade-offs of singly linked lists compared to indexed (array) lists?Indexed lists provide O(1) random access and near O(1) append (amortized), but are O(n) for operations at the front. Singly linked lists provide O(1) access/update at the head (head, tail, cons) and O(n) access/updates at the tail or by index. Choosing a structure means trading time across operations you perform most.
Why is in-place mutation discouraged, especially in concurrent programs?In-place updates make sharing unsafe, requiring complex synchronization that is prone to deadlocks, stale reads, and data races. Immutable structures avoid these hazards: updates create new versions, so readers safely see a consistent snapshot. “Stale data” is simply the snapshot at read time—equivalent to locking in mutable designs but simpler and safer.
What does “data sharing” mean in persistent lists, and which operations benefit?Data sharing reuses existing nodes between versions. For a singly linked list, head access is O(1), tail returns the rest in O(1), and cons (prepend) builds a new head pointing to the old list in O(1). No copying of the tail occurs, so common stack-like operations are fast and allocation is minimal.
How is an immutable singly linked list modeled in Kotlin?Use a sealed class with two cases: Nil (empty) and Cons(head, tail). Example sketch: sealed class List<out A>; internal object Nil : List<Nothing>(); internal class Cons<A>(val head: A, val tail: List<A>) : List<A>(). Provide constructors via a companion object and operations like isEmpty, cons, setHead, drop, dropWhile, reverse, etc.
What are foldRight and foldLeft, and when should I use each?foldRight processes from right to left (conceptually Cons(h, t) ⇒ f(h, foldRight(t))). It mirrors the list shape but is not tail recursive and can overflow the stack. foldLeft accumulates from left to right in a tail-recursive manner (stack-safe). Use foldLeft for stack safety on large lists; use foldRight when its natural shape or laziness (with lazy structures) is required, or implement a stack-safe variant via reversal or co-recursion.
How do you concatenate two persistent lists efficiently, and what are the limits?A simple recursive concat adds list1’s elements (in order) to the front of list2, yielding O(n1) time and sharing list2. However, the naïve recursive version can overflow the stack on large list1. Alternatives: implement concat via foldRight (same asymptotics, same stack caveat) or via foldLeft on reverse(list1) for stack safety (but with an extra reverse cost).
What variance issues arise with List<out A>, and how are cons/setHead implemented?Making List covariant (out A) allows Nil as List<Nothing> to be used as any List<A>, fixing pattern-matching like sum over List<Int>. But covariance forbids A in input positions. Solutions: implement mutator-like creators in the parent using @UnsafeVariance for parameters and ensure implementations don’t downcast values to Nothing; alternatively, split empty and non-empty cases with an Empty<A> base to avoid type checks.
Which higher-order operations are provided, and how are they implemented stack-safely?Common ops include drop, dropWhile, reverse, concat, flatten, map, filter, and flatMap. Stack-safe patterns: implement drop and reverse via tail recursion; implement map/filter/flatMap via foldLeft+reverse or via a stack-safe coFoldRight; define flatten as fold of concat or via flatMap(identity). Object notation (list.drop(2).setHead(0)) keeps call chains readable; companion or package-level functions can also be offered.

pro $24.99 per month

  • access to all Manning books, MEAPs, liveVideos, liveProjects, and audiobooks!
  • choose one free eBook per month to keep
  • exclusive 50% discount on all purchases
  • renews monthly, pause or cancel renewal anytime

lite $19.99 per month

  • access to all Manning books, including MEAPs!

team

5, 10 or 20 seats+ for your team - learn more


choose your plan

team

monthly
annual
$49.99
$499.99
only $41.67 per month
  • five seats for your team
  • access to all Manning books, MEAPs, liveVideos, liveProjects, and audiobooks!
  • choose another free product every time you renew
  • choose twelve free products per year
  • exclusive 50% discount on all purchases
  • renews monthly, pause or cancel renewal anytime
  • renews annually, pause or cancel renewal anytime
  • The Joy of Kotlin ebook for free
choose your plan

team

monthly
annual
$49.99
$499.99
only $41.67 per month
  • five seats for your team
  • access to all Manning books, MEAPs, liveVideos, liveProjects, and audiobooks!
  • choose another free product every time you renew
  • choose twelve free products per year
  • exclusive 50% discount on all purchases
  • renews monthly, pause or cancel renewal anytime
  • renews annually, pause or cancel renewal anytime
  • The Joy of Kotlin ebook for free
choose your plan

team

monthly
annual
$49.99
$499.99
only $41.67 per month
  • five seats for your team
  • access to all Manning books, MEAPs, liveVideos, liveProjects, and audiobooks!
  • choose another free product every time you renew
  • choose twelve free products per year
  • exclusive 50% discount on all purchases
  • renews monthly, pause or cancel renewal anytime
  • renews annually, pause or cancel renewal anytime
  • The Joy of Kotlin ebook for free