Overview

6 Dealing with optional data

This chapter explains why representing the absence of data is tricky in programs and how Kotlin encourages safer practices than relying on null pointers. It reviews the pitfalls of “business nulls” and sentinel values that silently blur intent and cause fragile code, then contrasts them with Kotlin’s nullable and non-nullable type system, which forces programmers to acknowledge and handle absence explicitly. The goal is to work with optional data in a way that avoids unchecked nulls, improves readability, and enables functional composition without scattering defensive checks.

To achieve this, the chapter develops an Option type with two cases: Some for the presence of a value and None for its absence. Building on this foundation, it introduces a small, composable toolkit: getOrElse (with eager and lazy defaults), map for transforming contained values, flatMap for chaining computations that may produce options, filter for conditional retention, and orElse for fallback options. It shows how to lift ordinary functions to operate over Option, combine multiple options with map2/map3, and adapt legacy APIs (including those that may throw exceptions). Practical examples include replacing null-returning map lookups with a getOption extension and calculating statistics like variance by composing operations that can short-circuit on missing input.

The chapter also demonstrates composing Option with List via sequence and traverse to invert List> into Option>, enabling all-or-nothing aggregation in a single pass. It closes with guidance on when to use each tool: nullable types may be fine for strictly local control flows (never to leak outside), while Option expresses truly optional data in public APIs. More generally, Option illustrates the monadic pattern—alongside List—where flatMap enables safe, declarative composition. Although many real-world absences stem from errors better modeled in richer types (introduced next), learning Option equips you with fundamental, reusable techniques for robust, null-free code.

Figure 6.1. Using an Option type for optional data allows you to compose functions even when the data is absent. Without the Option type, composing functions wouldn’t produce a function because the resulting program would potentially throw a NullPointerException.
Fig06 01
Figure 6.2. Lifting a function
Fig06 02

 Summary

  • You need a way to represent optional data, which means data that may or may not be present. The null pointer is the most impractical and dangerous way to represent the absence of data. Sentinel values and empty lists are other possible ways to represent the absence of data, but they don‘t compose well.
  • Kotlin uses nullable types to represent optional data. It protects you against NullPointerException when you use nonnullable types, and it forces you to handle null when using nullable types.
  • The Option data type is a much better way to represent optional data. The Some subtype represents data, and the None subtype represents the absence of data.
  • You can apply functions to Option through the map and flatMap higher-order functions, allowing for easy Option composition.
  • Functions operating on values can be lifted to operate on Option instances.
  • List can be composed with Option. A List<Option> can be turned into an Option<List> using the sequence function.
  • Option instances can be compared for equality. Instances of subtype Some are equal if their wrapped values are equal. Because there‘s only one instance of None, all instances of None are equal.
  • Although Option may represent the result of a computation producing an exception, all information about the occurring exception is lost. In the next chapter, you‘ll learn how to deal with this problem.

FAQ

Why are null references called the “billion dollar mistake” and what problems do they cause?Null references let “absence” flow through programs invisibly. They lead to NullPointerExceptions at use sites, force pervasive null checks, encourage “business nulls” and sentinel values that blur intent, and can silently mask earlier bugs (for example, passing null to APIs that accept it). They also make function composition brittle because any step can blow up when a null appears.
How does Kotlin’s type system help with null safety?Kotlin distinguishes non-nullable types (e.g., String) from nullable types (String?). You can only assign null to nullable types, and the compiler forces you to handle nulls with safe calls (?.), the Elvis operator (?:), etc. This pushes handling to the edges and prevents many accidental NullPointerExceptions, even though nulls can still arrive from libraries.
Why not use sentinel values or 0/1-element lists for optional results?Sentinel values overload meaning (for example, 0 or Double.NaN might be a real value), don’t generalize across types, and hide intent. Using lists with 0 or 1 element has no type-level guarantee against multiple elements and brings unnecessary list operations. Both approaches are error-prone and harm composability.
What is the Option type introduced in this chapter?Option is a sealed class representing optional data with two cases: Some(value) and None. It’s like a list with at most one element. A companion builder converts nullable values to Option: Option(a) yields None if a is null, otherwise Some(a). This creates a safe context for absence without using null.
How do I safely extract a value from Option?- Eager default: getOrElse(defaultValue) returns the contained value or the provided default. - Lazy default: getOrElse { computeDefault() } defers evaluation so the default is computed only for None. Example:
val x = max(list).getOrElse { expensiveFallback() }
Use the lazy version to avoid unnecessary work or side effects when the Option is Some.
How do I transform and compose Option values?- map(f): Option<A> → Option<B> applies f to the value if present. - flatMap(f): Option<A> → Option<B> where f: A → Option<B>, to chain computations that may fail. - filter(p): keeps the value only if it satisfies p, else None. - orElse { other }: provides a fallback Option lazily. These support safe, declarative composition without manual null checks.
How can I adapt existing APIs (that return nulls or throw) to Option?- Wrap nullable results: define extensions like Map.getOption(key) = Option(this[key]). - Lift pure functions: lift(f): (Option<A>) → Option<B> using map. - Lift exception-throwing functions: wrap in try/catch and return None on failure. - hLift: convert f: A → B into A → Option<B> to keep failure handling explicit.
How do I combine multiple Option values (e.g., two or three inputs)?Use map2/map3 with curried functions:
fun <A,B,C> map2(oa: Option<A>, ob: Option<B>,
                 f: (A) -> (B) -> C): Option<C> =
  oa.flatMap { a -> ob.map { b -> f(a)(b) } }
This pattern scales (swap the final map for flatMap if f returns Option). It lets you apply ordinary multi-arg functions to optional inputs safely.
How do I work with collections of Option values?Use sequence and traverse with your List type: - sequence(List<Option<A>>): Option<List<A>> returns Some(list) if all are Some, else None. - traverse(List<A>, f: A → Option<B>): Option<List<B>> maps with f and sequences in one pass (more efficient than map + sequence). These enable batch validation/decoding where any failure aborts the whole result.
When should I use Option vs Kotlin nullable types, and what’s the bigger picture?- Nullable types: fine for local/internal use (for example, loop termination). Don’t let nulls leak across function boundaries. - Option: use for truly optional data that isn’t an error. For absence caused by failures, prefer an error-carrying type (covered next chapter). Option (like List) forms a monad: it has a constructor (Option(a)) and flatMap, enabling safe, composable pipelines.

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