The Old Way: Manual Time Measurement
If you’ve ever benchmarked a function in Kotlin or Android, you’ve probably written something like this:
val start = System.currentTimeMillis()
val result = doExpensiveWork()
val elapsed = System.currentTimeMillis() - start
Log.d("Perf", "doExpensiveWork took ${elapsed}ms, result=$result")
It works, but it’s noisy. You need three lines just to time one call, and you have to name both the timing variable and the result variable separately. Kotlin 1.9 made this significantly cleaner with measureTimedValue and a mature, type-safe Duration API that deserves a lot more attention than it gets.
measureTimedValue: One Call, Two Results
measureTimedValue (from kotlin.time) executes a block, records how long it took, and returns both the result of the block and the elapsed time as a TimedValue<T> data class. You destructure it in one line:
import kotlin.time.measureTimedValue
val (result, duration) = measureTimedValue {
doExpensiveWork()
}
println("Result: $result, took: $duration")
// Result: 42, took: 134ms
The duration is a kotlin.time.Duration object — not a raw Long. That matters a lot, as we’ll see in a moment. There’s also measureTime if you only care about the elapsed time and not the return value:
import kotlin.time.measureTime
val elapsed = measureTime {
preloadImages()
}
println("Preload finished in $elapsed")
The Duration Type: Stop Guessing Units
The real gem here is kotlin.time.Duration. Raw millisecond Longs are a constant source of bugs — is this value in ms, seconds, or nanoseconds? Did you forget to multiply by 1000 somewhere? Duration eliminates all of that by making the unit part of the type.
You can create a Duration using extension properties on numeric types:
import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.seconds import kotlin.time.Duration.Companion.minutes import kotlin.time.Duration.Companion.hours val timeout = 30.seconds val cacheLifetime = 5.minutes val animDelay = 250.milliseconds val sessionMax = 2.hours // Arithmetic is unit-safe val totalTimeout = timeout + 10.seconds // 40s val half = cacheLifetime / 2 // 2m 30s println(timeout.inWholeSeconds) // 30 println(cacheLifetime.inWholeMilliseconds) // 300000
Durations are also naturally comparable and can be formatted for display:
val fast = 120.milliseconds
val slow = 3.seconds
println(fast < slow) // true
println(slow.toString()) // 3s
println(fast.toString()) // 120ms
// Components — useful for countdown timers
val bigDuration = 1.hours + 23.minutes + 45.seconds
bigDuration.toComponents { hours, minutes, seconds, _ ->
println("%02d:%02d:%02d".format(hours, minutes, seconds)) // 01:23:45
}
Practical Android Example: Logging Slow Operations
Here’s a wrapper you can drop into any Android project to log slow operations automatically, using both measureTimedValue and a Duration threshold:
import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.measureTimedValue
inline fun logIfSlow(
tag: String,
threshold: Duration = 100.milliseconds,
block: () -> T
): T {
val (result, duration) = measureTimedValue(block)
if (duration > threshold) {
Log.w(tag, "Slow operation detected: ${duration} (threshold: $threshold)")
}
return result
}
// Usage:
val user = logIfSlow("UserRepo", threshold = 50.milliseconds) {
userRepository.getUserById(id)
}
Clean, reusable, and entirely unit-safe. No chance of accidentally comparing milliseconds with seconds.
Using Duration With Coroutines
The Kotlin coroutines library has been updated to accept Duration directly wherever timeouts and delays used to take Longs. This makes coroutine code far more readable:
import kotlin.time.Duration.Companion.seconds
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.*
suspend fun fetchWithTimeout(): Data = withTimeout(10.seconds) {
apiService.fetchData()
}
suspend fun retryWithDelay(retries: Int) {
repeat(retries) { attempt ->
try {
return apiService.fetchData()
} catch (e: IOException) {
delay(500.milliseconds * (attempt + 1)) // exponential-ish backoff
}
}
}
Compare withTimeout(10.seconds) to withTimeout(10_000L) — the former is immediately obvious; the latter requires you to know (or remember) that the parameter is in milliseconds.
Bonus: Duration in WorkManager and AlarmManager
If you set up WorkManager constraints or periodic work, you typically deal with raw Long + TimeUnit pairs. You can convert your Duration to whatever the API needs:
import kotlin.time.Duration.Companion.hours
import java.util.concurrent.TimeUnit
val syncInterval = 6.hours
val request = PeriodicWorkRequestBuilder(
syncInterval.inWholeMinutes, TimeUnit.MINUTES
).build()
Summary
measureTimedValue and Kotlin’s Duration type are a small but meaningful quality-of-life upgrade for any Kotlin codebase. They replace ad-hoc Long arithmetic with a type-safe, self-documenting API that makes timing logic impossible to misread. If you work with performance monitoring, retry logic, coroutine timeouts, or periodic tasks, there’s almost no reason to use raw millisecond Longs anymore.
This post was written by a human with the help of Claude, an AI assistant by Anthropic.
