Question
I am testing concurrent goroutines in Go and want a function to pause for a random amount of time, up to one second. I added this line:
time.Sleep(rand.Int31n(1000) * time.Millisecond)
But the code does not compile, and I get this error:
invalid operation: rand.Int31n(1000) * time.Millisecond (mismatched types int32 and time.Duration)
Why does this happen, and what is the correct way to multiply a duration by an integer in Go?
Short Answer
By the end of this page, you will understand what time.Duration is in Go, why it cannot be multiplied directly by values of a different numeric type, and how to convert integers safely so they work with functions like time.Sleep. You will also see practical examples using random delays, learn common mistakes, and understand how this pattern appears in real Go code.
Concept
time.Duration in Go is a named type that represents a length of time as a number of nanoseconds.
For example:
var d time.Duration = time.Second
Even though time.Duration is based on an integer type internally, Go treats it as its own type. Go is strict about type safety, so values of different types usually cannot be mixed in arithmetic unless you convert them explicitly.
That is why this fails:
rand.Int31n(1000) * time.Millisecond
rand.Int31n(1000)returns anint32time.Millisecondis atime.Duration- Go does not automatically convert
int32totime.Duration
The fix is to convert the random number to time.Duration first:
time.Sleep(time.Duration(rand.Int31n(1000)) * time.Millisecond)
Now both sides of the multiplication are compatible:
time.Duration(rand.Int31n(1000))is atime.Durationtime.Millisecondis also atime.Duration
This matters in real programming because durations are used everywhere in Go:
- timeouts
- retries
- polling intervals
- backoff logic
- rate limiting
- benchmarking and simulation
Understanding how Go handles typed values helps you avoid compile errors and write clearer code.
Mental Model
Think of time.Duration as a measuring unit like centimeters.
If someone gives you the number 5, that is just a plain number. But if you want to say 5 centimeters, you must attach the unit.
In Go:
rand.Int31n(1000)is just a number of typeint32time.Millisecondis a time unit
Go does not guess that your plain number should become a duration. You must say it explicitly:
time.Duration(rand.Int31n(1000))
Now Go understands: “This number represents a duration count,” so multiplying by time.Millisecond makes sense.
So the mental model is:
- plain integers are just counts
time.Durationis a count with time meaning- convert the count before combining it with time units
Syntax and Examples
The basic pattern is:
time.Duration(number) * time.Unit
Correct syntax
time.Sleep(time.Duration(rand.Int31n(1000)) * time.Millisecond)
This means:
- generate a random number from
0to999 - convert it to
time.Duration - multiply it by
time.Millisecond - sleep for that amount of time
Example: fixed duration multiplication
package main
import (
"fmt"
"time"
)
func main() {
wait := time.Duration(3) * time.Second
fmt.Println(wait)
}
Output:
3s
Example: random delay
package main
import (
"math/rand"
"time"
)
func main() {
rand.Seed(time.Now().UnixNano())
delay := time.Duration(rand.Int31n(1000)) * time.Millisecond
time.Sleep(delay)
}
Example: using rand.Intn instead
A common alternative is to use rand.Intn, which returns int instead of int32:
delay := time.Duration(rand.Intn(1000)) * time.Millisecond
This still requires conversion to time.Duration, but many Go programs prefer Intn when int32 is not specifically needed.
Step by Step Execution
Consider this code:
package main
import (
"fmt"
"math/rand"
"time"
)
func main() {
rand.Seed(time.Now().UnixNano())
n := rand.Int31n(5)
delay := time.Duration(n) * time.Millisecond
fmt.Println("random number:", n)
fmt.Println("delay:", delay)
time.Sleep(delay)
fmt.Println("done")
}
Step by step:
-
rand.Seed(time.Now().UnixNano())- Initializes the random generator with a changing seed.
- Without this, the program may produce the same sequence each run.
-
n := rand.Int31n(5)- Picks a random
int32from0to4. - Example value:
3
- Picks a random
-
delay := time.Duration(n) * time.Millisecond- Converts
3fromint32totime.Duration - Multiplies it by
time.Millisecond - Result:
3ms
- Converts
-
fmt.Println("random number:", n)- Prints the original random number
-
fmt.Println("delay:", delay)- Prints the duration, such as
3ms
- Prints the duration, such as
-
time.Sleep(delay)- Pauses the program for that duration
-
fmt.Println("done")- Runs after the sleep finishes
The key step is the conversion:
time.Duration(n)
Without that conversion, the multiplication fails at compile time.
Real World Use Cases
Working with multiplied durations is common in Go applications.
Random delays for testing
You used this pattern for goroutine testing. That is a valid use case when you want to simulate:
- network latency
- slow database queries
- non-deterministic task completion
Example:
time.Sleep(time.Duration(rand.Intn(500)) * time.Millisecond)
Retry with backoff
Programs often wait longer after each failed attempt:
wait := time.Duration(attempt) * time.Second
Polling loops
An app may check a resource every few seconds:
interval := time.Duration(10) * time.Second
Rate limiting
A worker may pause between operations:
time.Sleep(200 * time.Millisecond)
Timeout configuration
Sometimes you compute durations from numeric config values:
secondsFromConfig := 15
%timeout := time.Duration(secondsFromConfig) * time.Second
This pattern is especially useful when values come from:
- config files
- environment variables
- command-line flags
- user input
Real Codebase Usage
In real Go codebases, developers usually compute durations in a small, readable variable first instead of putting everything directly inside time.Sleep.
Common pattern: assign first, use later
delay := time.Duration(rand.Intn(1000)) * time.Millisecond
time.Sleep(delay)
This is easier to debug and log.
Validation pattern
When durations come from config, developers often validate them:
seconds := 5
if seconds < 0 {
seconds = 0
}
timeout := time.Duration(seconds) * time.Second
Backoff and retry logic
for attempt := 1; attempt <= 3; attempt++ {
err := tryRequest()
if err == nil {
return
}
wait := time.Duration(attempt) * time.Second
time.Sleep(wait)
}
Jitter to avoid synchronized retries
In distributed systems, developers add randomness so many processes do not retry at exactly the same moment:
base := 2 * time.Second
jitter := time.Duration(rand.Intn(500)) * time.Millisecond
time.Sleep(base + jitter)
Guarding against bad units
A common real-world bug is forgetting the unit multiplier. Good code makes units explicit:
// Good
pollInterval := time.Duration(cfg.PollMs) * time.Millisecond
// Risky: meaning of 500 is unclear
pollInterval := time.Duration(500)
The first version is much clearer because it documents the intended unit.
Common Mistakes
1. Mixing numeric types without conversion
Broken code:
time.Sleep(rand.Int31n(1000) * time.Millisecond)
Why it fails:
rand.Int31n(1000)isint32time.Millisecondistime.Duration
Fix:
time.Sleep(time.Duration(rand.Int31n(1000)) * time.Millisecond)
2. Forgetting the time unit
Broken code:
time.Sleep(time.Duration(1000))
This sleeps for 1000 nanoseconds, not 1000 milliseconds.
Fix:
time.Sleep(time.Duration(1000) * time.Millisecond)
3. Assuming Go does automatic conversion
Broken idea:
var n int = 5
var d time.Duration = n * time.Second
This does not compile because n is int, not time.Duration.
Fix:
var n int = 5
var d time.Duration = time.Duration(n) * time.Second
4. Using random delays without seeding
Code:
rand.Intn(1000)
This may repeat the same sequence across runs depending on how the random generator is used.
Typical fix:
rand.Seed(time.Now().UnixNano())
5. Writing hard-to-read one-liners
This works, but is less readable:
time.Sleep(time.Duration(rand.Intn(1000)) * time.Millisecond)
Often clearer:
delay := time.Duration(rand.Intn(1000)) * time.Millisecond
time.Sleep(delay)
This is easier to inspect and test.
Comparisons
| Concept | What it is | Example | Notes |
|---|---|---|---|
int | General integer type | 5 | Used for counts, indexes, lengths |
int32 | 32-bit integer | rand.Int31n(1000) | Not interchangeable with time.Duration |
time.Duration | Time length in nanoseconds | 2 * time.Second | Used by time.Sleep, timeouts, intervals |
time.Duration vs plain integers
| Plain integer | time.Duration |
|---|---|
| Just a number | A number with time meaning |
| Cannot be passed directly to time APIs in mixed arithmetic | Designed for time calculations |
| May represent anything | Represents a duration in nanoseconds |
rand.Int31n vs rand.Intn
| Function | Return type | Example use |
|---|---|---|
rand.Int31n(n) | int32 | Useful when you specifically want int32 |
rand.Intn(n) | int | Common choice for general use |
For duration calculations, both still need conversion:
time.Duration(rand.Int31n(1000)) * time.Millisecond
time.Duration(rand.Intn(1000)) * time.Millisecond
Direct duration literal vs computed duration
| Style | Example | Best for |
|---|---|---|
| Direct | 500 * time.Millisecond | Fixed delays |
| Computed | time.Duration(n) * time.Millisecond | Dynamic values from variables or random numbers |
Cheat Sheet
Core rule
When combining numbers with time.Duration, convert the number first.
time.Duration(n) * time.Millisecond
Common examples
wait := time.Duration(5) * time.Second
sleep := time.Duration(rand.Intn(1000)) * time.Millisecond
timeout := time.Duration(configSeconds) * time.Second
Why your original code failed
rand.Int31n(1000) * time.Millisecond
- left side:
int32 - right side:
time.Duration - Go does not mix them automatically
Correct version
time.Sleep(time.Duration(rand.Int31n(1000)) * time.Millisecond)
Remember the unit
time.Duration(1000) // 1000 nanoseconds
time.Duration(1000) * time.Millisecond // 1000 milliseconds
Useful units
time.Nanosecond
time.Microsecond
time.Millisecond
time.Second
time.Minute
time.Hour
Readable pattern
delay := time.Duration(rand.Intn(1000)) * time.Millisecond
time.Sleep(delay)
Safe habit
If a value is meant to represent time, make the unit explicit.
FAQ
Why do I need to convert to time.Duration in Go?
Because Go does not automatically mix different numeric types in arithmetic. time.Duration is a distinct type.
What type is time.Millisecond?
It is a constant of type time.Duration.
Does rand.Intn() also need conversion?
Yes. Even though it returns int, you still need time.Duration(rand.Intn(...)) before multiplying by a duration unit.
What happens if I write time.Duration(1000)?
That means 1000 nanoseconds, because time.Duration stores nanoseconds unless you multiply by a unit like time.Millisecond.
Is 500 * time.Millisecond valid without conversion?
Yes. Untyped numeric constants like 500 can work directly with time.Duration constants.
Should I use rand.Int31n or rand.Intn?
Usually rand.Intn is simpler unless you specifically need an int32 result.
Can I add durations together?
Yes.
wait := 2*time.Second + 500*time.Millisecond
Can I divide durations in Go?
Yes.
half := 10 * time.Second / 2
Mini Project
Description
Build a small Go program that simulates multiple worker goroutines finishing at random times. This demonstrates how to create computed delays with time.Duration and use them safely with time.Sleep.
Goal
Create a program that starts several goroutines, gives each one a random delay in milliseconds, and prints when each worker starts and finishes.
Requirements
[
"Start at least 3 goroutines.",
"Each goroutine must sleep for a random duration between 0 and 999 milliseconds.",
"Use time.Duration(...) * time.Millisecond to build the delay.",
"Print a message before and after each sleep.",
"Wait for all goroutines to finish before exiting."
]
Keep learning
Related questions
Are Go Maps Passed by Value or Reference?
Learn Go map parameter and return semantics: maps are passed by value, while copies share underlying data. Know when map pointers are useful.
Authentication for Go REST APIs: Sessions, Tokens, and Middleware
Learn practical authentication patterns for Go REST APIs, including password hashing, server sessions, bearer tokens, middleware, and security rules.
Automatic Build Versioning in Go: Embed Incrementing Build Numbers
Learn how to add automatic build versioning in Go using linker flags, build metadata, CI counters, and Git-based version values.