iKit
Technical · 9 min read ·

How a Lorem Ipsum Generator Works: The Word Bank (2026)

Every lorem ipsum generator is a small word bank plus a few rules. Here is the actual algorithm: 182 Latin words, sentence windows, commas, punctuation.

How a Lorem Ipsum Generator Works: The Word Bank (2026)

How a Lorem Ipsum Generator Works: The Word Bank

Click "generate" on a lorem ipsum generator and you get Latin-looking prose that reads like real text but says nothing. There is no corpus behind it and no language model. It is a fixed list of words, a random number generator, and about forty lines of code. Once you have seen the algorithm, you can predict exactly why the output looks the way it does — and where it falls short.

TL;DR

  • A generator is a word bank, a sentence-length range, and comma rules.
  • Emmet's Latin dictionary is 182 words plus an 8-word opening phrase.
  • Words come from Cicero's de Finibus 1.10.32–1.10.33, deduplicated.
  • Sentences sample without replacement, so no word repeats inside one.
  • Only seeded generators like Faker produce reproducible filler text.

How does a lorem ipsum generator work?

Strip away the UI and every generator does the same four things in the same order.

The three parts every generator has

First, a word bank: a flat array of tokens. Second, a common phrase: the lorem ipsum dolor sit amet… opener, stored separately so it can be turned on or off. Third, a set of shape rules — how many words per sentence, how many commas, which punctuation mark ends it.

That is the whole design. Lipsum.com describes its own engine the same way, as a dictionary of over 200 Latin words combined with a handful of model sentence structures.

The generation loop, step by step

The loop in Emmet's lorem module is short enough to read in one sitting:

paragraph(dict, wordCount):
  if startWithCommon:
    emit sentence(common[0..wordCount], ".")
  while total < wordCount:
    n = min(rand(2, 30), wordCount - total)
    emit sentence(sample(dict.words, n))

rand(from, to) is exclusive at the top, so a sentence gets between 2 and 29 words. The final sentence is clipped so the paragraph lands on the exact requested count rather than overshooting.

Why generators sample without replacement

Emmet's sample() builds a Set, not an array. Words are drawn until the set reaches the target size, which means a single sentence never contains the same word twice. That is not realistic — real prose repeats function words constantly — but it makes short filler look less broken. Faker takes the opposite approach and allows repeats, which is why a long faker.lorem.paragraphs() block visibly recycles et, ut, and qui.

Where do the Latin words in lorem ipsum come from?

The word bank is not arbitrary. It is a vocabulary list extracted from one passage of classical Latin.

Cicero's de Finibus, sections 1.10.32 and 1.10.33

Per lipsum.com's own reference page, the source is de Finibus Bonorum et Malorum, written by Cicero in 45 BC, specifically sections 1.10.32 and 1.10.33. Read those two sections and the generator's dictionary stops looking random: accusamus, iusto, dignissimos, ducimus, blanditiis, praesentium, voluptatum, deleniti, atque and corrupti all appear there, in that order, in a single sentence of 1.10.33.

We covered the full paper trail — Cicero, the 1914 Rackham translation, Letraset in 1966 — in a separate article linked at the bottom. What matters here is the mechanical consequence: the word bank is the passage's vocabulary with duplicates removed.

How 182 words became the standard bank

Emmet's latin.json ships exactly 182 entries in its words array, plus 8 in common. Deduplicating two paragraphs of Cicero gets you to roughly that number, and every generator that copied its dictionary from an earlier generator inherited the same ceiling. That is why filler from different tools feels interchangeable: they are drawing from near-identical lists.

Why the word list is not really Latin

The bank contains real Latin lemmas, but the assembled sentences have no grammar. Cases do not agree, verbs do not conjugate to their subjects, and word order carries no meaning. That is deliberate — the point is a plausible distribution of word lengths and letter frequencies, not a translatable sentence. It is also why you should mark filler with lang="la" in HTML so screen readers do not attempt to pronounce it as English.

Why does lorem ipsum always start with "Lorem ipsum dolor sit amet"?

Because the opener is hard-coded, and it is the only part of the output that is not random.

The common array and the skipCommon flag

Emmet stores the opener as its own array and prepends it to the first sentence unless you opt out:

{
  "common": ["lorem", "ipsum", "dolor", "sit",
             "amet", "consectetur", "adipisicing",
             "elit"],
  "words": ["exercitationem", "perferendis", "…"]
}

Note the spelling: adipisicing, with an extra i. The 1966 Letraset passage reads adipiscing, and Cicero's original reads adipisci velit. Somewhere in the copy chain a typo entered a dictionary file and then propagated to every editor that bundles Emmet. If your placeholder text says adipisicing, you now know which generator produced it.

When the opener gets dropped

The module exposes a skipCommon option, and it also suppresses the opener when a lorem node is generated in a repeating context. The practical effect is that the familiar phrase belongs to the first sentence of a block, not to every paragraph. Turn it off entirely and the output is still valid filler — it just stops announcing itself.

lorem vs lorem100 vs p*4>lorem

Per the Emmet documentation, the bare lorem abbreviation expands to a 30-word block, and a numeric suffix overrides the count:

lorem          → 30 words
lorem100       → 100 words
p*4>lorem      → four filled paragraphs
ul>lorem6*5    → five list items, 6 words each

The last form relies on Emmet's implicit tag resolver, which is why you can drop the li.

What makes generated filler look like real prose

Three shape rules do all the work. Get them wrong and the output reads as noise.

Sentence length: the rand(2, 30) window

A uniform draw from 2 to 29 words produces an average sentence around 15 words — close enough to English editorial prose that a paragraph block looks typeset rather than random. Faker uses a tighter default, { min: 3, max: 10 } for sentence(), and composes a paragraph() from 3 sentences by default. Tighter windows read as punchier copy; wider windows produce the ragged look designers expect from body text.

Comma density scales with sentence length

Emmet buckets sentences and assigns a comma budget:

Sentence length Commas inserted
2–3 words 0
4–6 words 0
7–12 words 0 or 1
13+ words 1 to 3

Commas are placed at random positions, never on the final word, and never doubled on a word that already has one. It is crude, and it works: comma frequency, not comma placement, is what the eye reads as "this is a sentence".

Punctuation weighting, and an off-by-one worth knowing

The terminal mark is chosen from the string ?!... — one question mark, one exclamation mark, three periods — which was clearly meant to make periods three times more likely. But the helper indexes with rand(0, val.length - 1), and since rand is already exclusive at the top, the last character is unreachable. The effective distribution is one period in two, not three in five:

Intended Actual
. 60% . 50%
? 20% ? 25%
! 20% ! 25%

Harmless in filler text. A useful reminder that "pick a random element" is one of the easiest functions in the world to get subtly wrong — the same class of bug that shows up in UUID and token generation, where it is not harmless at all.

How to generate the same lorem ipsum every time

Most in-browser generators call Math.random(), which JavaScript does not let you seed. Every run gives you new text. That is fine for a mockup and useless for a test suite.

Seeded generators with Faker

Faker solves this with a pluggable randomizer. Its documented default since v9 is a 53-bit Mersenne Twister, and seeding it makes the whole pipeline deterministic:

import { faker } from '@faker-js/faker';

faker.seed(42);
const a = faker.lorem.paragraphs(2);

faker.seed(42);
const b = faker.lorem.paragraphs(2);
// a === b, on every machine

The Faker lorem API exposes the full ladder — word(), words(), sentence(), sentences(), lines(), paragraph(), paragraphs(), plus slug() for URL-safe hyphenated strings.

Math.random() versus a Mersenne Twister

Property Math.random() Faker's randomizer
Seedable No Yes
Reproducible across runs No Yes
Good enough for filler Yes Yes
Safe for secrets No No

Neither is cryptographically secure. If you need unpredictable output rather than merely varied output, that is a different tool entirely — see the password generator, which draws from crypto.getRandomValues().

When determinism actually matters

Three cases justify the extra dependency: snapshot tests, where regenerated filler creates false diffs on every run; visual regression suites, where a longer paragraph shifts layout and fails the pixel comparison; and shared fixtures, where two developers debugging the same seeded dataset need identical text. Everywhere else — mockups, CMS drafts, spacing checks — unseeded output is fine and one less package to install.

Choosing a generator by what it actually does

Generator Word bank Seedable
Emmet lorem 182 + 8 opener No
Faker lorem Several hundred Yes
Lipsum.com 200+ No
iKit Lorem Ipsum Classic passage No

The practical difference is repetition. A 182-word bank starts visibly recycling vocabulary somewhere past 600 words, so if you are filling a long-form article template, generate several smaller blocks rather than one enormous one. Paste the result into a word counter to hit an exact target, or into a Markdown editor if you want the filler already wrapped in headings and lists.

References

Related on iKit

Related posts