skip to content

What does Duration.toComponents do, and how would you use it to render a duration like '1h 23m 45s'?

level: seniorimportance: should knowfreq 30%

answer

  1. toComponents passes fields to a lambda; no tuple returned
  2. Largest field is Long; smaller fields are bounded Int
  3. inline -> no allocation, primitives
  4. Overloads: down to days/hours/minutes/seconds
  5. Negative duration -> negative components

basics

~10 s

toComponents splits a duration into separate fields like hours, minutes, seconds, and nanoseconds, handing them to a lambda you provide. You then format those numbers into a human-readable string.

solid answer

~40 s

Duration.toComponents is a set of inline overloads that decompose a duration into whole sub-fields and pass them to a lambda (it does NOT return a tuple). The richest overload gives (days, hours, minutes, seconds, nanoseconds) where each field except the largest is already bounded to its natural range (hours 0..23, minutes 0..59, etc.) and the largest carries any overflow. Other overloads stop at hours, minutes, or seconds. Because it is inline and the receiver values are primitives, there is no boxing/allocation. You return whatever the lambda produces (often a String). Sign handling: for a negative duration the components are negative. For locale-aware or unit-suffixed output you usually still build the string yourself, or use toString(unit, decimals) for a single-unit fixed-precision render.

code

kotlin · 11 lines
kotlin
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds

fun Duration.clock(): String =
    toComponents { h, m, s, _ ->
        "%02d:%02d:%02d".format(h, m, s)
    }

fun main() {
    println(5025.seconds.clock())  // 01:23:45
}

go deeper

for a junior

Knows there is a way to split a duration into hours/minutes/seconds for display.

for a middle

Can call toComponents with the right overload and build a formatted string from the fields.

for a senior

Explains the lambda-based inline (no-allocation) design, field bounding, the Long-largest-field rule, and negative handling.

for a principal

Weighs toComponents vs toString variants for a formatting layer, and reasons about allocation-free decomposition in hot rendering paths.

## What toComponents is `Duration.toComponents` is a family of **inline** functions that break a duration into its constituent whole units and invoke a caller-supplied lambda with them. Crucially it **does not return a data class or Pair** — it calls your `action` and returns whatever that lambda returns. This design avoids allocating a holder object (it's `inline`, fields are primitives). ## The overloads ```kotlin // largest-first; each smaller field is bounded to its range, largest absorbs overflow fun <T> toComponents(action: (days: Long, hours: Int, minutes: Int, seconds: Int, nanoseconds: Int) -> T): T fun <T> toComponents(action: (hours: Long, minutes: Int, seconds: Int, nanoseconds: Int) -> T): T fun <T> toComponents(action: (minutes: Long, seconds: Int, nanoseconds: Int) -> T): T fun <T> toComponents(action: (seconds: Long, nanoseconds: Int) -> T): T ``` The **first** parameter is a `Long` (it can be arbitrarily large); the rest are `Int` already constrained (`hours` 0..23, `minutes` 0..59, `seconds` 0..59, `nanoseconds` 0..999_999_999). ## Rendering 1h 23m 45s ```kotlin import kotlin.time.Duration import kotlin.time.Duration.Companion.seconds fun Duration.humanize(): String = toComponents { hours, minutes, seconds, _ -> buildString { if (hours > 0) append("${hours}h ") if (minutes > 0) append("${minutes}m ") append("${seconds}s") } } fun main() { println((5025.seconds).humanize()) // 1h 23m 45s } ``` ## Sign handling For a **negative** duration every component is negative (e.g. `-1h -23m`). If you need a sign-aware display, branch on `isNegative()` and format `absoluteValue` with a leading `-`. ## Related formatting helpers - `toString()` — default debug form, e.g. `1h 23m 45s` already (multi-unit, abbreviated). - `toString(unit: DurationUnit, decimals: Int)` — single-unit fixed-precision, e.g. `d.toString(DurationUnit.SECONDS, 2)` -> `5025.00s`. - `toIsoString()` — ISO-8601, covered separately. ## When to reach for toComponents When you need **custom** layout (e.g. `01:23:45`, padded fields, omit-zero rules) that the built-in `toString` can't produce. For a quick abbreviated form, the default `toString()` is often enough.

  • Does toComponents return a Pair or data class of fields?
    No. It is an inline function that passes the components to your lambda and returns the lambda's result. That avoids allocating a holder object.
  • What range is the 'minutes' parameter in the (hours, minutes, seconds, nanoseconds) overload?
    0..59 — every field except the first (largest) is bounded to its natural range; the first field is a Long that absorbs all overflow.

Like a coin-counting machine that hands you separate piles (dollars, quarters, dimes) instead of one total — you decide how to label and stack them.

saying these in an interview costs you the question

  • Claiming toComponents returns a tuple/Pair instead of invoking a lambda
  • Expecting the smaller fields to be unbounded rather than 0..59 etc.
  • Forgetting the largest field is a Long that carries overflow
  • Not handling negative durations (components go negative)

context