Bulk random string generator
Thousands of random strings in one pass, generated in chunks so the tab stays responsive, with a progress bar and a cancel that works.
More options
The batch size is not the interesting part
We audited the fifteen generators that rank on page one for this family of terms, and their batch limits split three ways. Eight stop at 500 or fewer, four allow thousands, and three have no limit at all, which is worse than a low one: pinetools, codebeautify and onlinestringtools freeze the tab instead. The table has the individual numbers.
So "we go past 100" is not a claim worth making. What is worth measuring is what a large batch costs and whether the tool tells you the truth about what it did. gigacalculator's maximum batch took 16.808 seconds and returned 10,067,383 bytes of HTML, because every generation there is a full-page POST; three 8-character strings still cost about 1.6 seconds. pinetools took 2,113 ms to produce a million 30-character strings when its function was run in Node, and that is a lower bound: in a browser, with no chunking, it is a hang you cannot stop.
Three of the fifteen quietly return something other than what you asked for. Ask textfixer for 20,000 and it returns 1,000. Ask unit-conversion.info for a million and it truncates to 10,000, using an error element that is never populated. Ask uuidgenerator.net for 501 and it returns 10. None of them says so.
| Tool | Largest batch | What happens at the boundary |
|---|---|---|
| jam.dev, browserling | 1 | No quantity field exists |
| miniwebtool | 50 | Capped in the form |
| randomkeygen, generate-random.org, uuidtools, commentpicker | 100 | Capped in the form |
| uuidgenerator.net | 500 | A request for 501 returns 10, with no message |
| textfixer | 10,000 | A request for 20,000 returns 1,000, with no message |
| unit-conversion.info | 10,000 | Silently truncated; its error element is never filled |
| gigacalculator | 10,000 x 1,000 | Delivered, in 16.808 s and 10,067,383 bytes of HTML |
| pinetools, codebeautify, onlinestringtools | No limit | The tab freezes |
generate-random.org and uuidtools also rate limit at 60 requests per minute, verified by a probe that received HTTP 429 on request 61. Both generate on a server, so every batch is a network round trip.
How a large batch runs here
Everything is computed in the page. There is no request to make, so there is no rate limit, no queue and no server-side copy of your output. Open the network panel before you press generate and it stays empty.
The work is done in chunks. The generator produces a slice, hands the main thread back for one animation frame, repaints the progress bar and continues. That structure is what makes the cancel button real: cancellation is checked between chunks, so it takes effect within a frame rather than after the batch finishes. None of the fifteen has chunking, a progress bar or a cancel control, and the three without a cap cannot have them, because a single synchronous loop leaves nothing else able to run.
Entropy is drawn in bulk. crypto.getRandomValues has a 65,536-byte quota per call, and one call per character would mean 320,000 calls for a batch of 10,000 strings of 32 characters. The pool here is a Uint32Array filled in 4,096-word chunks, refilled about 78 times for that batch, and each word is mapped onto the alphabet by rejection sampling rather than a bare modulo.
The published target for that batch is under 200 milliseconds, with no chunk holding the main thread longer than one frame. Measured timings at 100, 1,000 and 10,000 are on the methodology page, because a speed claim you cannot check is worth exactly what a randomness claim you cannot check is worth.
If a configuration cannot be satisfied, the tool says so before it starts, names the reason, and leaves your previous output on screen. It never returns a smaller batch and calls it done.
Uniqueness across a batch
Asking for unique strings and getting strings that are unlikely to repeat are different things, and which one you need depends on length. At the default here, 16 characters from 62, each string carries 95.27 bits, and you would need roughly 31 trillion of them before the chance of one duplicate anywhere in the set reached 1 percent. The unique option changes nothing you could observe.
Short strings are the whole failure regime, and they are where people generate in bulk: coupon codes, referral codes, PINs, test fixtures. At four characters the space is 14,776,336, and a batch of 10,000 contains a duplicate about 96.6 percent of the time. That is the birthday bound, not a defect in anyone's random source.
The competitive picture is mixed rather than uniformly bad. gigacalculator's unique option is real and correct: a probe at length 1,000 over 62 characters requesting 10,000 returned 10,000 distinct values. Batch dedupe is parity with them, not novelty. It is absent from the other fourteen, three of which were measured directly: pinetools returned 533 duplicates in 200,000 four-character strings, against a birthday expectation of about 488; unit-conversion.info returned 1,545 distinct values of 2,000, against 1,559 expected under sampling with replacement; commentpicker pushes every value unconditionally while a filterDuplicatesFromArray helper sits unused in the bundle its page loads.
Deduplicating by redraw costs more as the request approaches the size of the space, and the tool shows that rather than hiding it. At half the space you average 1.39 draws per accepted string, at 90 percent 2.56, at 99 percent 4.65.
| Length | Entropy per string | Possible values | Batch size for a 1 percent duplicate chance |
|---|---|---|---|
| 4 | 23.82 bits | 14,776,336 | 545 |
| 6 | 35.73 bits | 5.68 x 10^10 | 33,789 |
| 8 | 47.63 bits | 2.18 x 10^14 | 2.09 million |
| 12 | 71.45 bits | 3.23 x 10^21 | 8.05 billion |
| 16 | 95.27 bits | 4.77 x 10^28 | 31.0 trillion |
| 32 | 190.53 bits | 2.27 x 10^57 | 6.76 x 10^27 |
The feasibility check
A loop that redraws until it holds enough distinct values never terminates when the request is impossible. Ask for 10,000 unique strings of two characters over a 62-character alphabet and only 3,844 distinct strings exist, so the loop spins for as long as the tab is open.
This tool sizes the space first, refuses before generating anything, names how many distinct strings the configuration can produce, and names the three settings you can change to make the request possible. Nothing already on screen is replaced by a failure. gigacalculator does this too and names the same remedies; the difference is that the check here is a comparison of two numbers in the page rather than a form submission.
Getting the batch out, and the spreadsheet hazard
Output can be emitted as plain lines, a chosen separator, a JSON array, JSON objects, TSV, quoted CSV, SQL INSERT rows, .env lines, a shell array, a Python list, a JavaScript array literal or numbered lines, and downloaded as .txt, .csv or .json. Nothing is gated. miniwebtool ships CSV and JSON downloads but puts both behind a rewarded video advert or a paid plan, which is the honest thing to criticise there rather than the absence of the feature.
It is not true that the rest are newline-only: pinetools has a free-text separator field and textfixer has three separator radios. What none of the fifteen has is a structured format, and neither of those two escapes a separator that occurs inside a generated string.
The CSV path carries a hazard. Excel and Google Sheets evaluate a cell whose value begins with =, + or -, and Excel treats a leading @ the same way, so an unescaped CSV of generated values can execute its own contents when someone opens it. With the alphanumeric default on this page that cannot happen, because none of those four characters is a letter or a digit. Enable symbols or paste in a custom character set and it can. The export quotes every field, prefixes an affected value with a single apostrophe so the spreadsheet reads it as text, and says on screen that it did, because a token whose shape changed on the way to disk is a bug of its own if you do not know about it. Use the JSON or plain-lines export if you need the exact bytes.
An exported file holds live secret material. It sits in your downloads folder, is picked up by the operating system search index, and goes into the next backup. Delete it when you are done. randomkeygen says the same on its own export, and almost nobody else does.
Doing this in your own code
JavaScript
// One pooled draw for the whole batch, not one syscall per character.
// crypto.getRandomValues has a 65,536-byte quota per call, so the pool
// is refilled in chunks rather than sized to the request.
function bulkStrings(count, length, alphabet) {
const n = alphabet.length;
const limit = Math.floor(2 ** 32 / n) * n; // rejection threshold
const pool = new Uint32Array(4096);
let i = pool.length; // forces a refill on first use
const draw = () => {
for (;;) {
if (i === pool.length) { crypto.getRandomValues(pool); i = 0; }
const v = pool[i++];
if (v < limit) return v % n; // discard the biased tail
}
};
const out = new Array(count);
for (let s = 0; s < count; s++) {
let str = "";
for (let c = 0; c < length; c++) str += alphabet[draw()];
out[s] = str;
}
pool.fill(0); // leftover words are key material
return out;
} Without the limit check, v % n is modulo biased, because 2 ** 32 is not a multiple of 62.
JavaScript, non-blocking
// Yield to the browser between chunks. This is what makes a progress bar
// paint and a cancel button work: both are checked between chunks, so
// they take effect within one frame instead of after the whole batch.
async function bulkChunked(count, length, alphabet, onProgress, signal) {
const out = [];
const CHUNK = 2000;
while (out.length < count) {
if (signal && signal.aborted) throw new DOMException("cancelled", "AbortError");
const n = Math.min(CHUNK, count - out.length);
for (const s of bulkStrings(n, length, alphabet)) out.push(s);
onProgress(out.length / count);
await new Promise(requestAnimationFrame);
}
return out;
} A generator that runs one synchronous loop cannot offer a cancel button, because nothing else can run while the loop is running.
Python 3.6+
import secrets, string
ALPHABET = string.ascii_letters + string.digits # 62 characters
def bulk(count, length=16):
return ["".join(secrets.choice(ALPHABET) for _ in range(length))
for _ in range(count)]
# Unique across the batch, with the feasibility check FIRST.
def bulk_unique(count, length=16):
space = len(ALPHABET) ** length
if count > space:
raise ValueError(
f"only {space:,} distinct strings exist at length {length}"
)
out = set()
while len(out) < count:
out.add("".join(secrets.choice(ALPHABET) for _ in range(length)))
return out Skip the check and the while loop never returns when the request exceeds the size of the space. secrets.choice rejection samples internally. random.choice rejection samples too, but it runs on the Mersenne Twister and is not cryptographically secure.
Go
import (
"crypto/rand"
"math/big"
)
const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
func bulk(count, length int) ([]string, error) {
out := make([]string, count)
max := big.NewInt(int64(len(alphabet)))
for i := range out {
b := make([]byte, length)
for j := range b {
n, err := rand.Int(rand.Reader, max)
if err != nil {
return nil, err
}
b[j] = alphabet[n.Int64()]
}
out[i] = string(b)
}
return out, nil
} crypto/rand.Int rejection samples for you. math/rand is a different package with a different guarantee and must not be substituted.
Rust
// rand = "0.9"
use rand::Rng;
const ALPHABET: &[u8] =
b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
fn bulk(count: usize, length: usize) -> Vec<String> {
let mut rng = rand::rng();
(0..count)
.map(|_| {
(0..length)
.map(|_| ALPHABET[rng.random_range(0..ALPHABET.len())] as char)
.collect()
})
.collect()
} random_range rejection-samples, so there is nothing to fix here. rand::rng() is a ChaCha12 CSPRNG seeded from the operating system. On rand 0.8 the two names are rand::thread_rng() and gen_range.
Shell
# 100 strings of 16 characters, straight from the kernel CSPRNG.
LC_ALL=C tr -dc 'A-Za-z0-9' < /dev/urandom | fold -w 16 | head -n 100
# Unique across the batch, with the count checked afterwards.
LC_ALL=C tr -dc 'A-Za-z0-9' < /dev/urandom | fold -w 16 | head -n 200 \
| sort -u | head -n 100 | wc -l
# If you write them to disk, overwrite and unlink afterwards. shred's own
# manual cautions that it assumes the filesystem and hardware overwrite in
# place, which many do not, and that backups may still hold a copy.
shred -u tokens.txt LC_ALL=C is load bearing. In a multibyte locale tr can reject input bytes as invalid and the pipeline stalls.
Export formats
Plain lines
kQ2mZ8vRt4XbN1pW
7yLcF0jHsDe5Ma3U
JSON array
["kQ2mZ8vRt4XbN1pW", "7yLcF0jHsDe5Ma3U"]
.env lines
API_KEY_1=kQ2mZ8vRt4XbN1pW
API_KEY_2=7yLcF0jHsDe5Ma3U
SQL INSERT
INSERT INTO api_keys (token) VALUES
('kQ2mZ8vRt4XbN1pW'),
('7yLcF0jHsDe5Ma3U');
CSV, every field quoted
index,value
1,"kQ2mZ8vRt4XbN1pW"
2,"7yLcF0jHsDe5Ma3U" In the CSV export a value beginning with =, +, - or @ is additionally prefixed with a single apostrophe, so a spreadsheet treats it as text rather than a formula.
Questions
How many strings can I generate at once?
The quantity field goes to 10,000 and the ceiling is stated on screen rather than applied behind your back. Past that the constraint is your own machine rather than a server quota, because nothing here is transmitted.
Will a large batch freeze my browser?
No. Generation runs in chunks and releases the main thread between them, so the page keeps responding, the progress bar keeps painting and cancel takes effect within one frame. Three of the fifteen generators we audited do freeze the tab, because they have no cap and run one synchronous loop.
Are the strings in a batch guaranteed to be unique?
Only if you switch on the unique option, which deduplicates across the whole batch rather than within a chunk. Without it, uniqueness is probabilistic and depends on length: at 16 characters over 62 you would need about 31 trillion strings before a 1 percent chance of a duplicate, but at four characters a batch of 10,000 contains one about 96.6 percent of the time.
Why does it sometimes refuse a configuration?
Because the request is impossible rather than merely slow. If you ask for more unique strings than the length and alphabet can produce, no amount of retrying will succeed, so the tool refuses before generating anything and tells you how many distinct strings the configuration allows.
Is the CSV download safe to open in Excel?
Yes, with one caveat worth knowing. Spreadsheets execute a cell that starts with =, + or -, and Excel also treats a leading @ that way. The export quotes every field and prefixes any affected value with an apostrophe, and says on screen when it has done so. If you need the exact bytes, use the JSON or plain-text export.