How do you design time-dependent code so it is deterministic and testable, using Kotlin's Clock?
answer
- Clock is an interface -> inject it
- Constructor param defaulting to Clock.System
- Test clock returns fixed/advanceable Instant
- runTest skips delays; Clock controls now() — different concerns
- TimeSource.Monotonic for elapsed, not Clock
basics
~10 sDon't call Clock.System.now() deep inside logic. Depend on the Clock interface, pass Clock.System in production, and pass a fake Clock that returns a fixed instant in tests so results are predictable.
solid answer
~40 skotlin.time.Clock is an interface with now(): Instant, which makes the current moment an injectable dependency rather than a hidden global. In production you wire Clock.System; in tests you supply a fake or controllable Clock returning fixed/advanceable instants, so time-sensitive branches (expiry, throttling, timestamps) become deterministic. Inject the Clock at the class boundary (constructor parameter, optionally defaulting to Clock.System) instead of scattering Clock.System.now() calls, which are untestable global state. For coroutines you separately get virtual time via runTest and TestDispatcher (delay-skipping), but that controls scheduling, not the wall-clock now() — so you still inject a Clock for timestamps. Keep zones explicit too. This is the classic 'ambient context as a seam' technique: time, like randomness, should be a dependency.
code
kotlin · 16 linesimport kotlin.time.*
class MutableClock(var current: Instant) : Clock {
override fun now() = current
fun advance(by: Duration) { current += by }
}
class RateLimiter(private val clock: Clock = Clock.System, private val window: Duration) {
private var last: Instant? = null
fun allow(): Boolean {
val now = clock.now()
val ok = last?.let { now - it >= window } ?: true
if (ok) last = now
return ok
}
}go deeper
Knows you can pass a fake Clock in tests to fix 'now'.
Injects Clock via constructor defaulting to Clock.System and writes a deterministic test.
Distinguishes Clock from coroutine virtual time and from TimeSource for elapsed measurement.
Establishes time-as-dependency conventions across the codebase, choosing the right primitive per concern and keeping zones explicit.
## The anti-pattern Calling `Clock.System.now()` (or `System.currentTimeMillis()`) buried inside business logic makes that logic depend on hidden global state. You can't assert 'token expires after 15 minutes' reliably because 'now' keeps moving. ## The fix: Clock as a dependency `kotlin.time.Clock` is an interface: ```kotlin interface Clock { fun now(): Instant } ``` Because it's an interface, the 'current time' becomes an **injectable seam**. ```kotlin class TokenService(private val clock: Clock = Clock.System) { fun isExpired(token: Token): Boolean = clock.now() >= token.issuedAt + 15.minutes } ``` Production passes nothing (defaults to `Clock.System`); tests pass a controllable clock. ## A controllable test clock ```kotlin import kotlin.time.* class MutableClock(var current: Instant) : Clock { override fun now() = current fun advance(by: Duration) { current += by } } @Test fun expiresAfter15min() { val clock = MutableClock(Instant.fromEpochSeconds(0)) val token = Token(issuedAt = clock.now()) val svc = TokenService(clock) clock.advance(16.minutes) assertTrue(svc.isExpired(token)) } ``` Now the test is deterministic and fast — no real waiting. ## Clock vs coroutine virtual time These solve different problems: - **`Clock`** controls what `now()` returns — i.e. **timestamps / wall-clock decisions**. - **`runTest` + `TestDispatcher`** (kotlinx.coroutines) control **scheduling**: `delay()` is skipped/advanced via virtual time so a test that 'waits 10s' finishes instantly. They compose: in a coroutine test you use `runTest` to skip delays **and** inject a `Clock` so any timestamps recorded are deterministic. One does not replace the other. ## TimeSource for elapsed measurement If the goal is measuring **how long** something took (not 'what time is it'), prefer `TimeSource.Monotonic` / `measureTime` — a monotonic source unaffected by wall-clock adjustments. `Clock.System.now()` can jump backward if the system clock is corrected, so don't use it for durations. ## Principles - Treat **time like randomness**: a dependency, not a static call. - Inject at the **boundary** (constructor), default to `Clock.System`. - Keep **TimeZone** explicit too (no `currentSystemDefault()` in logic). - Use the **right tool**: `Clock` for now(), `TimeSource` for elapsed, `runTest` for delay-skipping.
- Does runTest from kotlinx.coroutines make Clock.System.now() deterministic?No. runTest controls coroutine scheduling/virtual time for delays; it does not change what Clock.System.now() returns. Inject a Clock separately for deterministic timestamps.
- Why not use Clock.System.now() to measure how long an operation took?The system wall clock can be adjusted (NTP, manual changes) and even move backward; use the monotonic TimeSource (e.g. measureTime) for reliable elapsed measurement.
Injecting a Clock is like giving a chemistry experiment a controllable timer instead of waiting for the real wall clock to tick.
saying these in an interview costs you the question
- Calling Clock.System.now() deep inside business logic with no seam
- Believing runTest replaces an injectable Clock for timestamps
- Using wall-clock now() to measure elapsed time
- Mocking static time via reflection instead of dependency injection
- Hardcoding the system zone inside the logic under test