Question
I want to generate a random string of a fixed length in Go using only alphabetic characters (A-Z and a-z), with no digits or symbols. What is the simplest and fastest way to do this?
Short Answer
By the end of this page, you will understand how to generate random strings in Go, how to limit the characters to uppercase and lowercase letters only, and when to choose a simple approach versus a more secure one.
Concept
Random string generation in Go usually means:
- choosing a set of allowed characters
- picking characters from that set at random
- repeating until the string reaches the required length
For this question, the allowed character set is only letters:
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
The core idea is that a random string is built one character at a time. For each position in the string, your program randomly selects one character from the allowed alphabet.
This matters in real programming because random strings are often used for:
- temporary identifiers
- invite codes
- test data
- filenames
- mock tokens
In Go, there are two common ways to generate random values:
math/randfor general-purpose pseudo-random valuescrypto/randfor security-sensitive randomness
If you only want a simple random-looking string for testing or non-sensitive use, math/rand is often enough. If the string is used for passwords, reset tokens, API secrets, or anything security-related, use crypto/rand instead.
A fixed-length random string is not about generating one random word. It is about repeatedly sampling from a known alphabet until you have enough characters.
Mental Model
Imagine you have a bag containing 52 tiles:
- 26 lowercase letters
- 26 uppercase letters
To build a random string of length 8:
- reach into the bag
- pick one tile
- write it down
- put the tile back
- repeat 8 times
That is exactly what the code does. The string length tells you how many picks to make, and the alphabet string is the bag of available characters.
Syntax and Examples
Basic approach with math/rand
package main
import (
"fmt"
"math/rand"
"time"
)
const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
func randomString(n int) string {
rand.Seed(time.Now().UnixNano())
b := make([]byte, n)
for i := range b {
b[i] = letters[rand.Intn(len(letters))]
}
return string(b)
}
func main() {
fmt.Println(randomString(10))
}
How it works
lettersstores all allowed characters.make([]byte, n)creates space forncharacters.rand.Intn(len(letters))picks a random index.- Each byte in
bis filled with one random letter. string(b)converts the byte slice into a Go string.
Better structure: seed once
Seeding inside the function is simple, but in real code it is better to seed the random generator once.
package main
import (
"fmt"
"math/rand"
"time"
)
const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
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(8))
fmt.Println(randomString(8))
}
This avoids reseeding on every call.
Secure version with crypto/rand
Use this if the randomness matters for security.
package main
import (
"crypto/rand"
"fmt"
"math/big"
)
const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
func secureRandomString(n int) (string, error) {
b := make([]byte, n)
for i := range b {
n, err := rand.Int(rand.Reader, big.NewInt(int64(len(letters))))
if err != nil {
return "", err
}
b[i] = letters[n.Int64()]
}
return string(b), nil
}
func main() {
s, err := secureRandomString(10)
if err != nil {
panic(err)
}
fmt.Println(s)
}
This version is slower than math/rand, but appropriate for secrets and tokens.
Step by Step Execution
Consider this function:
const letters = "abcXYZ"
func randomString(n int) string {
b := make([]byte, n)
for i := range b {
b[i] = letters[rand.Intn(len(letters))]
}
return string(b)
}
If we call:
rand.Seed(1)
fmt.Println(randomString(4))
Here is the step-by-step idea:
-
letterscontains 6 possible characters:abcXYZ
-
make([]byte, 4)creates a byte slice with space for 4 characters. -
The loop runs 4 times, once for each position.
-
On each loop:
rand.Intn(6)returns a number from0to5- that number is used as an index into
letters - the selected letter is stored in
b[i]
-
Example sequence of chosen indexes:
2, 5, 0, 4
- That would map to:
c, Z, a, Y
- The byte slice becomes:
['c', 'Z', 'a', 'Y']
string(b)converts it into:
"cZaY"
The exact output changes depending on the random values, but the process is always the same: random index -> character lookup -> build string.
Real World Use Cases
Random fixed-length letter strings are useful in many situations:
- Test data generation
- Create fake usernames like
AbCdEf.
- Create fake usernames like
- Temporary file labels
- Generate readable suffixes for filenames.
- Invite or referral codes
- If security is not critical, a short random letter code may be enough.
- Mock API responses
- Simulate IDs or labels in development.
- Games and simulations
- Generate random names, room codes, or labels.
If the string protects access to something important, do not use math/rand. Use crypto/rand instead.
Real Codebase Usage
In real projects, developers usually wrap this logic in a small helper function so it can be reused across the codebase.
Common patterns include:
Validation
Check that the requested length is valid.
func randomString(n int) string {
if n <= 0 {
return ""
}
b := make([]byte, n)
for i := range b {
b[i] = letters[rand.Intn(len(letters))]
}
return string(b)
}
Early return
Guard clauses keep the function simple.
if n <= 0 {
return ""
}
Reusable alphabet configuration
Developers often make the allowed characters configurable.
func randomFromCharset(n int, charset string) string {
if n <= 0 || len(charset) == 0 {
return ""
}
b := make([]byte, n)
for i := range b {
b[i] = charset[rand.Intn(len(charset))]
}
return string(b)
}
Error handling for secure randomness
When using crypto/rand, return an error instead of hiding failures.
func secureRandomString(n int) (string, error) {
// return error if crypto random generation fails
return "", nil
}
Utility packages
In larger codebases, this logic often lives in packages such as:
utilinternal/randompkg/helpers
The main goal is to avoid rewriting the same loop in many places.
Common Mistakes
1. Seeding every time the function is called
This is a very common beginner mistake.
Problematic code
func randomString(n int) string {
rand.Seed(time.Now().UnixNano())
b := make([]byte, n)
for i := range b {
b[i] = letters[rand.Intn(len(letters))]
}
return string(b)
}
Why it is a problem
- It repeats work unnecessarily.
- Calls made very close together can behave poorly.
- Seeding should usually happen once.
Better
func main() {
rand.Seed(time.Now().UnixNano())
fmt.Println(randomString(8))
}
2. Using math/rand for secrets
Broken choice
// Not suitable for password reset tokens
code := randomString(32)
Why it is a problem
math/rand is predictable compared with cryptographic randomness.
Better
Use crypto/rand for anything security-sensitive.
3. Forgetting to restrict the character set
If you want letters only, your alphabet must contain only letters.
Wrong idea
const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
This includes digits, so the result is not letters-only.
4. Not handling zero or negative lengths
Risky code
func randomString(n int) string {
b := make([]byte, n)
return string(b)
}
Negative values will panic when creating the slice.
Better
func randomString(n int) string {
if n <= 0 {
return ""
}
b := make([]byte, n)
for i := range b {
b[i] = letters[rand.Intn(len(letters))]
}
return string(b)
}
5. Confusing bytes and runes
For plain English letters, bytes are fine because each character is one byte in UTF-8. If you later use non-ASCII characters, the approach may need to change.
Comparisons
| Approach | Best for | Fast | Secure | Notes |
|---|---|---|---|---|
math/rand + letters string | Simple random strings, tests, demos | Yes | No | Easiest option |
crypto/rand + letters string | Tokens, passwords, sensitive values | No | Yes | Slower but secure |
| Random bytes encoded as hex/base64 | Binary-safe IDs | Yes/Medium | Can be | Usually includes digits or symbols |
math/rand vs crypto/rand
| Feature | math/rand | crypto/rand |
|---|---|---|
| Purpose | Simulation, games, non-secure randomness | Security-sensitive randomness |
| Speed | Faster | Slower |
| Predictability | Can be predicted | Designed to be unpredictable |
| Error handling | Usually no error on number generation | Functions often return errors |
Fixed alphabet vs generated character ranges
| Method | Example | Pros | Cons |
|---|---|---|---|
| Fixed alphabet string | letters[rand.Intn(len(letters))] | Clear and easy to control | Manual character list |
| Character ranges | random between a-z or A-Z | Can be compact | More logic needed to combine uppercase and lowercase cleanly |
For beginners, a fixed alphabet string is usually the simplest and most readable choice.
Cheat Sheet
const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
Simple function
func randomString(n int) string {
if n <= 0 {
return ""
}
b := make([]byte, n)
for i := range b {
b[i] = letters[rand.Intn(len(letters))]
}
return string(b)
}
Seed once
rand.Seed(time.Now().UnixNano())
Secure version
- Use
crypto/rand - Return
(string, error) - Use it for tokens, secrets, and passwords
Rules to remember
- Use a fixed alphabet when you want only certain characters.
- Use
math/randfor non-sensitive randomness. - Use
crypto/randfor security-sensitive randomness. - Seed
math/randonce, not on every function call. - Check
n <= 0to avoid invalid lengths. - Bytes are fine for ASCII letters like
A-Zanda-z.
FAQ
How do I generate a random string with letters only in Go?
Create a string containing all allowed letters, then repeatedly pick random indexes from it and build a result string.
Is math/rand good enough for random strings in Go?
Yes for testing, demos, and non-sensitive values. No for passwords, tokens, or anything security-related.
How do I include both uppercase and lowercase letters?
Use an alphabet like:
const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
Why should I seed math/rand only once?
Seeding initializes the pseudo-random generator. Doing it once is cleaner and avoids unnecessary repeated setup.
Should I use bytes or runes for this?
Use bytes if your alphabet is only ASCII letters. Use runes if you need Unicode characters.
What happens if the requested length is 0?
A good helper function should return an empty string.
How can I make the function reusable for other character sets?
Pass the character set as a parameter, such as randomFromCharset(n, charset).
Mini Project
Description
Build a small Go utility that generates random invitation codes made of uppercase and lowercase letters only. This demonstrates how to define an allowed character set, pick random characters, validate input, and reuse the logic in a practical program.
Goal
Create a Go program that generates one or more fixed-length random letter-only codes from the command line.
Requirements
- Create a function that generates a random string of a given length using only
A-Zanda-z. - Return an empty string if the requested length is 0 or negative.
- Seed the random generator once before generating codes.
- Print at least three random codes of the same length.
- Keep the implementation limited to non-secure randomness using
math/rand.
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.