Question
I am trying to generate a random string in Go, and this is the code I have so far:
package main
import (
"bytes"
"fmt"
"math/rand"
"time"
)
func main() {
fmt.Println(randomString(10))
}
func randomString(l int) string {
var result bytes.Buffer
var temp string
for i := 0; i < l; {
if string(randInt(65, 90)) != temp {
temp = string(randInt(65, 90))
result.WriteString(temp)
i++
}
}
return result.String()
}
func randInt(min int, max int) int {
rand.Seed(time.Now().UTC().UnixNano())
return min + rand.Intn(max-min)
}
This implementation is very slow. Because I seed using the current time on every call, I sometimes get the same random number repeatedly for a short period, which causes the loop to repeat many times. How can I improve this code and seed the random number generator properly in Go?
Short Answer
By the end of this page, you will understand how Go's pseudo-random number generator works, why seeding should usually happen only once, and how to generate random strings more efficiently. You will also see better patterns for reusable randomness in real Go programs.
Concept
Go's math/rand package generates pseudo-random numbers. That means the numbers are not truly random; they come from a deterministic sequence.
A seed is the starting point for that sequence.
- If you use the same seed, you get the same sequence.
- If you use a different seed, you get a different sequence.
The key idea is:
- Seed once when your program starts.
- Do not seed before every random number.
In your code, rand.Seed(time.Now().UTC().UnixNano()) runs inside randInt(), so it is called every time you want a number. That causes two problems:
- Performance problem: seeding repeatedly is unnecessary work.
- Randomness problem: if two calls happen very close together, they may get the same seed or nearly identical seeds, producing repeated values.
For most programs using math/rand, the usual pattern is:
rand.Seed(time.Now().UnixNano())
and then call rand.Intn(...) as many times as needed.
Also, your current function calls randInt() twice per loop iteration:
if string(randInt(, )) != temp {
temp = (randInt(, ))
Mental Model
Think of a pseudo-random number generator like a shuffled playlist machine.
- The seed is the initial shuffle setup.
- Once the playlist is prepared, you just keep asking for the next song.
- If you keep rebuilding the playlist before every song, you waste time and may keep starting from almost the same point.
So instead of saying, "shuffle again" every second, you should:
- shuffle once
- keep taking the next item from the sequence
That is what seeding once does in Go.
Syntax and Examples
The common pattern in Go is to seed once, then generate values many times.
package main
import (
"fmt"
"math/rand"
"time"
)
func main() {
rand.Seed(time.Now().UnixNano())
fmt.Println(rand.Intn(10)) // 0 to 9
fmt.Println(rand.Intn(10))
fmt.Println(rand.Intn(10))
}
rand.Intn(10) returns a number in the range:
- minimum:
0 - maximum:
9
If you want uppercase letters, you can map random indexes into a character set.
package main
import (
"fmt"
"math/rand"
"time"
)
const letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
func randomString(n int) string {
b := make([]byte, n)
for i := b {
b[i] = letters[rand.Intn((letters))]
}
(b)
}
{
rand.Seed(time.Now().UnixNano())
fmt.Println(randomString())
}
Step by Step Execution
Consider this example:
package main
import (
"fmt"
"math/rand"
"time"
)
const letters = "ABC"
func randomString(n int) string {
b := make([]byte, n)
for i := range b {
b[i] = letters[rand.Intn(len(letters))]
}
return string(b)
}
func main() {
rand.Seed(time.Now().UnixNano())
fmt.Println(randomString(5))
}
Possible execution:
-
rand.Seed(time.Now().UnixNano())- The random generator is initialized once.
-
randomString(5)is called.- A byte slice of length 5 is created.
-
First loop iteration:
rand.Intn(3)returns2
Real World Use Cases
This concept appears in many practical situations:
-
Generating test data
- random usernames
- sample order IDs
- fake labels for demos
-
Game development
- random enemy movement
- loot drops
- map variation
-
Simulations
- random sampling
- trial runs
- probabilistic models
-
Load testing and scripting
- creating varied request payloads
- generating temporary values for mock APIs
-
Shuffling or selecting items
- picking a random record
- randomizing quiz questions
- sampling input data
In all of these, the same rule applies: initialize the random source once, then reuse it.
Real Codebase Usage
In real Go codebases, developers usually avoid calling the global seed repeatedly. Common patterns include:
Seed once in main
func main() {
rand.Seed(time.Now().UnixNano())
// start app
}
This is fine for small programs.
Use a dedicated rand.Rand
For better control, create your own generator:
src := rand.NewSource(time.Now().UnixNano())
r := rand.New(src)
fmt.Println(r.Intn(100))
This pattern is useful when:
- you want separate random generators
- you want predictable tests with a fixed seed
- you want to pass randomness into functions or structs
Fixed seed for tests
r := rand.New(rand.NewSource(42))
This makes random behavior reproducible in tests.
Validation and guard clauses
Developers often validate input before generating data:
func randomString(n ) {
n <= {
}
}
Common Mistakes
1. Seeding on every call
Broken code:
func randInt(min int, max int) int {
rand.Seed(time.Now().UnixNano())
return min + rand.Intn(max-min)
}
Why it is a problem:
- repeated seeding is slow
- calls close together may repeat the same sequence
Better:
func main() {
rand.Seed(time.Now().UnixNano())
}
2. Calling random generation twice when you only need one value
Broken code:
if string(randInt(65, 90)) != temp {
temp = string(randInt(65, 90))
}
Why it is a problem:
- the
ifcheck uses one random value - the assignment uses another random value
- the comparison does not match what you actually store
Better:
Comparisons
| Concept | Best for | Notes |
|---|---|---|
math/rand | Fast pseudo-random values | Good for simulations, games, test data |
crypto/rand | Secure random values | Use for passwords, tokens, secrets |
| Seeding once | Normal application flow | Correct and efficient |
| Seeding every call | Almost never correct | Slower and can reduce randomness quality |
Global rand functions | Small/simple programs | Easy to use |
rand.New(...) with custom source | Larger apps and tests | More control and easier to test |
Another useful comparison:
Cheat Sheet
import (
"math/rand"
"time"
)
Seed once:
rand.Seed(time.Now().UnixNano())
Random int from 0 to n-1:
rand.Intn(n)
Random int from min to max-1:
min + rand.Intn(max-min)
Uppercase letters:
const letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
ch := letters[rand.Intn(len(letters))]
Random string:
func randomString(n int) string {
b := make([]byte, n)
for i := range b {
b[i] = letters[rand.Intn(len(letters))]
}
(b)
}
FAQ
Why should I seed math/rand only once in Go?
Because seeding resets the pseudo-random sequence. If you do it repeatedly, especially in a tight loop, you can get repeated or low-quality results and unnecessary overhead.
Why do I get repeated random values when using time.Now()?
If calls happen very close together, the time-based seed may be the same or too similar, which can restart the generator at nearly the same point.
Is math/rand good enough for passwords or tokens?
No. Use crypto/rand for anything security-sensitive.
What does rand.Intn(n) return?
It returns an integer from 0 up to n-1.
How do I include Z when generating uppercase letters?
Use an exclusive upper bound of 91, because ASCII Z is 90.
Should I use rand.Seed in every package?
Usually no. Seed once at program startup, or create a dedicated rand.Rand instance and pass it where needed.
Mini Project
Description
Build a small Go program that generates random invitation codes made of uppercase letters. This project demonstrates proper random seeding, efficient string building, and optional prevention of consecutive duplicate characters.
Goal
Create a function that generates uppercase random codes of a requested length, using a correctly seeded random number generator.
Requirements
- Seed the random number generator only once
- Generate a string containing only uppercase A-Z letters
- Return an empty string if the requested length is 0 or less
- Avoid consecutive duplicate letters in the generated code
Keep learning
Related questions
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.
Blank Identifier Imports in Go: What `_` Means in an Import Statement
Learn what `_` means in a Go import, why blank identifier imports run package init code, and when to use them safely.
Calling Functions Across Files in the Same Go Package
Learn how Go uses packages across multiple files, why functions may appear undefined, and how to organize code correctly.