Question

How to Generate a Random String in Ruby

rubyrandompasswords

Question

I want to generate an 8-character pseudo-random string in Ruby.

Right now I am building an uppercase string from "A".."Z" like this:

value = ""
8.times { value << (65 + rand(25)).chr }

I also tried extending it to mixed-case letters ("a".."z" and "A".."Z") like this:

value = ""
8.times { value << ((rand(2) == 1 ? 65 : 97) + rand(25)).chr }

This works, but it is hard to read and awkward to pass around because it is not written as a clean single expression.

What is a clearer and more idiomatic Ruby way to generate a random string, especially for mixed-case alphabetic characters?

Short Answer

By the end of this page, you will understand how to generate random strings in Ruby in a cleaner and more idiomatic way. You will learn how to build character pools, sample from them, join results into a string, and when to use SecureRandom instead of rand.

Concept

Ruby makes string generation easier when you think in terms of choosing characters from a set instead of manually working with ASCII codes.

Your original code uses numeric character codes like 65 and 97, then converts them back into letters with .chr. That works, but it is harder to read and maintain.

A more Ruby-friendly approach is:

  1. Define the allowed characters.
  2. Randomly pick characters from that set.
  3. Join them into one string.

For example, if your allowed characters are uppercase and lowercase letters, you can create a pool like this:

chars = [('a'..'z'), ('A'..'Z')].map(&:to_a).flatten

Then select 8 random characters and join them:

8.times.map { chars.sample }.join

This matters because in real programs you often need random strings for:

  • temporary codes
  • test data
  • file names
  • invitation codes
  • non-secure identifiers

If the string is used for security-sensitive purposes like tokens, password reset links, or API secrets, you should use SecureRandom instead of plain rand. rand is fine for simple pseudo-random values, but it is not designed for cryptographic security.

Mental Model

Think of random string generation like drawing letters from a bag.

  • The bag contains all allowed characters.
  • Each draw picks one random character.
  • After enough draws, you combine the letters into a string.

Using ASCII codes is like saying, "Take item number 65 from the warehouse." It works, but it is harder to understand.

Using ranges like ('a'..'z').to_a is like labeling the bag with actual letters so anyone reading the code can immediately see what is inside.

Take Quiz

Syntax and Examples

Basic idiomatic approach

chars = [('a'..'z'), ('A'..'Z')].map(&:to_a).flatten
random_string = 8.times.map { chars.sample }.join

puts random_string

How it works

  • ('a'..'z').to_a creates ['a', 'b', ..., 'z']
  • ('A'..'Z').to_a creates ['A', 'B', ..., 'Z']
  • map(&:to_a).flatten combines both arrays into one character pool
  • 8.times.map { chars.sample } picks 8 random characters
  • join turns the array of characters into a string

Cleaner one-liner

8.times.map { [('a'..'z'), ('A'..'Z')].map(&:to_a).flatten.sample }.join

This is a single expression, but repeating the character pool inside the block is inefficient and harder to read. It is usually better to build the pool once.

Better reusable method

def random_letters(length = 8)
  chars = [('a'..'z'), ('A'..'Z')].flat_map(&:to_a)
  Array.new(length) { chars.sample }.join
end

puts random_letters
puts random_letters(12)

Why this version is better

  • reusable
  • easy to read
  • accepts different lengths
  • clearly shows the allowed characters

Using SecureRandom for stronger randomness

If you need a token or code that should be harder to guess:

require 'securerandom'

token = SecureRandom.alphanumeric(8)
puts token

This may include digits as well as letters. If you specifically need only letters, you can still build a custom pool and sample from it, but for security-sensitive use cases SecureRandom is the better source of randomness.

Step by Step Execution

Consider this example:

def random_letters(length = 8)
  chars = [('a'..'z'), ('A'..'Z')].flat_map(&:to_a)
  Array.new(length) { chars.sample }.join
end

puts random_letters(5)

Step-by-step

  1. Ruby defines the method random_letters.
  2. The argument length defaults to 8, but here we pass 5.
  3. ('a'..'z').to_a creates an array of lowercase letters.
  4. ('A'..'Z').to_a creates an array of uppercase letters.
  5. flat_map(&:to_a) combines both into one array like:
["a", "b", ..., "z", "A", "B", ..., "Z"]
  1. Array.new(length) { chars.sample } runs the block 5 times.
  2. Each time, chars.sample returns one random letter.
  3. Ruby creates an array of 5 random letters, for example:
["q", "M", "z", "A", "k"]
  1. join combines them into:
"qMzAk"
  1. puts prints the final string.

Each run will usually produce a different result.

Real World Use Cases

Random strings appear in many practical Ruby programs.

Common uses

  • Temporary file names
    • Avoid collisions when creating local files.
  • Invitation codes
    • Generate short codes users can type manually.
  • Seed data and testing
    • Create fake usernames or identifiers in scripts and specs.
  • Reference IDs
    • Produce simple non-sensitive labels for internal workflows.
  • Short URLs or slugs
    • Build compact identifiers, often with a custom character set.

Example: simple invite code

def invite_code(length = 8)
  chars = [('A'..'Z'), ('a'..'z')].flat_map(&:to_a)
  Array.new(length) { chars.sample }.join
end

puts invite_code

Example: test data

def fake_username
  chars = ('a'..'z').to_a
  Array.new(6) { chars.sample }.join
end

puts fake_username

For anything that protects accounts, data, or access, prefer SecureRandom.

Take Quiz

Real Codebase Usage

In real projects, developers usually wrap random string logic in a method, service object, or utility module instead of scattering inline code everywhere.

Common patterns

Helper method

def random_string(length: 8, chars: [('a'..'z'), ('A'..'Z')].flat_map(&:to_a))
  Array.new(length) { chars.sample }.join
end

This makes the function configurable and reusable.

Validation of inputs

def random_string(length: 8)
  raise ArgumentError, 'length must be positive' unless length > 0

  chars = [('a'..'z'), ('A'..'Z')].flat_map(&:to_a)
  Array.new(length) { chars.sample }.join
end

This uses a guard clause to reject bad input early.

Configuration of allowed characters

UPPER = ('A'..'Z').to_a
LOWER = ('a'..'z').to_a
DIGITS = ('0'..'9').to_a

def random_code(length: 10)
  chars = UPPER + LOWER + DIGITS
  Array.new(length) { chars.sample }.join
end

This is common when teams want to control which characters are allowed.

Security-sensitive tokens

require 'securerandom'

def api_token
  SecureRandom.hex(16)
end

Real applications often separate:

  • human-readable random codes using custom pools
  • security tokens using SecureRandom

That distinction is important in production code.

Common Mistakes

1. Off-by-one errors with rand

Your original code uses rand(25).

value << (65 + rand(25)).chr

This generates values from 0 to 24, so one letter is missing.

Correct version

value << (65 + rand(26)).chr

If you use arrays and sample, you avoid this kind of bug.

2. Using ASCII codes when direct characters are clearer

Harder to read

8.times.map { (65 + rand(26)).chr }.join

Easier to read

chars = ('A'..'Z').to_a
8.times.map { chars.sample }.join

3. Rebuilding the character pool every time unnecessarily

Less efficient

8.times.map { [('a'..'z'), ('A'..'Z')].flat_map(&:to_a).sample }.join

This recreates the same array on every iteration.

Better

chars = [('a'..'z'), ('A'..'Z')].flat_map(&:to_a)
8.times.map { chars.sample }.join

4. Using rand for secrets

Not ideal for security

def reset_token
  chars = [('a'..'z'), ('A'..'Z')].flat_map(&:to_a)
  Array.new(20) { chars.sample }.join
end

Better for security-sensitive tokens

require 'securerandom'

def reset_token
  SecureRandom.alphanumeric(20)
end

5. Forgetting that sample can repeat characters

sample chooses randomly each time, so duplicates are possible.

"aAaBBcDd"

That is usually fine. But if you need unique characters, use a different approach such as shuffling.

chars = [('a'..'z'), ('A'..'Z')].flat_map(&:to_a)
unique_string = chars.shuffle.take(8).join

Comparisons

ApproachExampleBest forNotes
ASCII codes + chr(65 + rand(26)).chrLow-level controlWorks, but less readable
Character array + samplechars.sampleGeneral string generationClear and idiomatic
Array.new(length) { ... }.joinArray.new(8) { chars.sample }.joinReusable string buildingVery readable
times.map { ... }.join8.times.map { chars.sample }.joinShort scriptsAlso common and clear
SecureRandom.alphanumericSecureRandom.alphanumeric(8)Security-sensitive tokensIncludes digits
shuffle.take(n).joinchars.shuffle.take(8).joinUnique characters onlyCannot exceed pool size

rand vs SecureRandom

FeaturerandSecureRandom
Built into basic Ruby usageYesRequires securerandom
Good for simple random stringsYesYes
Good for secrets and tokensNoYes
Cryptographically secureNoYes

times.map vs Array.new

StyleExampleNotes
times.map8.times.map { chars.sample }.joinShort and expressive
Array.newArray.new(8) { chars.sample }.joinSlightly more explicit about array creation

Both are valid Ruby style.

Take Quiz

Cheat Sheet

Generate random uppercase letters

chars = ('A'..'Z').to_a
Array.new(8) { chars.sample }.join

Generate random mixed-case letters

chars = [('a'..'z'), ('A'..'Z')].flat_map(&:to_a)
Array.new(8) { chars.sample }.join

Reusable method

def random_letters(length = 8)
  chars = [('a'..'z'), ('A'..'Z')].flat_map(&:to_a)
  Array.new(length) { chars.sample }.join
end

Secure version

require 'securerandom'
SecureRandom.alphanumeric(8)

Unique characters only

chars = [('a'..'z'), ('A'..'Z')].flat_map(&:to_a)
chars.shuffle.take(8).join

Rules to remember

  • Prefer character ranges over ASCII numbers for readability.
  • sample picks one random element from an array.
  • join combines an array of characters into a string.
  • rand is fine for non-sensitive randomness.
  • Use SecureRandom for tokens, secrets, and anything security-related.
  • sample may repeat characters.
  • shuffle.take(n) gives unique characters, but only up to the pool size.

FAQ

How do I generate a random string in Ruby?

Create an array of allowed characters, pick random elements with sample, and join them into a string.

What is the most idiomatic Ruby way to build a random letter string?

A common Ruby approach is:

chars = [('a'..'z'), ('A'..'Z')].flat_map(&:to_a)
Array.new(8) { chars.sample }.join

Should I use rand or SecureRandom in Ruby?

Use rand for simple non-sensitive randomness. Use SecureRandom for tokens, passwords, reset links, or anything that should be hard to guess.

How do I generate only uppercase random letters in Ruby?

chars = ('A'..'Z').to_a
Array.new(8) { chars.sample }.join

How do I avoid duplicate characters in a random string?

Use shuffle.take(length).join instead of repeated sample, as long as the requested length does not exceed the number of available characters.

Why is using ASCII codes less preferred in Ruby?

It is harder to read and easier to get wrong. Character ranges like ('A'..'Z') make the intent clearer.

Can I generate a random string in one line?

Yes:

Array.new(8) { [('a'..'z'), ('A'..'Z')].flat_map(&:to_a).sample }.join

But it is usually better to define the character pool once for clarity and efficiency.

Take Quiz

Mini Project

Description

Build a Ruby helper that generates invite codes for a small application. The helper should support uppercase-only codes and mixed-case codes so you can reuse it in scripts or simple apps. This demonstrates how to define character pools, generate a string of a given length, and keep the code readable.

Goal

Create a reusable Ruby method that generates random alphabetic codes with configurable length and character set.

Requirements

  • Create a method that accepts a length argument.
  • Support an uppercase-only mode.
  • Support a mixed-case mode.
  • Return the generated code as a single string.
  • Print a few example codes to show that it works.
Take Quiz

Keep learning

Related questions

View more