I can't say that I know the combinatorics for this. Inspired by the thoughtful answer written by David G. Stork, here are some heuristic observations. Perhaps someone can write an authoritative answer and add/utilize some of the constraints presented here to speed up their searches.
Let's start by defining a function:
wordDelimitedBy[a_String, b_String] :=
DictionaryLookup[
StartOfString ~~ a ~~ Longest[__] ~~ b ~~ EndOfString]
Usage Examples
1- Words ending in "q" (none):
wordDelimitedBy[#, "q"] & /@ Alphabet[]
{{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {},
{}, {}, {}, {}, {}, {}, {}, {}, {}}
2- Words ending in "j":
wordDelimitedBy[#, "j"] & /@ Alphabet[]
{}, {}, {}, {}, {}, {}, {}, {"haj", "hajj"}, {}, {}, {}, {}, {}, {},
{}, {}, {}, {"raj"}, {}, {}, {}, {}, {}, {}, {}, {}
Essentially no words there either.
To find the character tuples that do not delimit any words:
delimCounts = {First@#, Last@#,
Length@wordDelimitedBy[First@#, Last@#]} & /@
Tuples[Alphabet[], {2}] ;
nolinks = Cases[delimCounts, {a_, b_, 0} :> {a, b}]
{{"a", "j"}, {"a", "q"}, {"a", "v"}, {"b", "j"}, {"b", "q"}, {"b",
"v"}, {"c", "j"}, {"c", "q"}, {"c", "v"}, {"d", "j"}, {"d", "q"},
{"d", "u"}, {"e", "j"}, {"e", "q"}, {"e", "v"}, {"f", "j"}, {"f",
"q"}, {"f", "v"}, {"g", "j"}, {"g", "q"}, {"h", "q"}, {"h", "v"},
{"i", "j"}, {"i", "q"}, {"i", "v"}, {"i", "z"}, {"j", "j"}, {"j",
"q"}, {"j", "v"}, {"k", "j"}, {"k", "q"}, {"k", "v"}, {"k", "x"},
{"l", "j"}, {"l", "q"}, {"l", "z"}, {"m", "j"}, {"m", "q"}, {"m",
"v"}, {"n", "j"}, {"n", "q"}, {"n", "u"}, {"n", "v"}, {"n", "z"},
{"o", "j"}, {"o", "q"}, {"o", "v"}, {"o", "z"}, {"p", "j"}, {"p",
"q"}, {"q", "b"}, {"q", "j"}, {"q", "q"}, {"q", "u"}, {"q", "v"},
{"q", "w"}, {"q", "x"}, {"r", "q"}, {"r", "u"}, {"s", "j"}, {"s",
"q"}, {"t", "j"}, {"t", "q"}, {"t", "v"}, {"u", "b"}, {"u", "j"},
{"u", "q"}, {"u", "u"}, {"u", "v"}, {"u", "z"}, {"v", "j"}, {"v",
"q"}, {"v", "u"}, {"v", "v"}, {"v", "z"}, {"w", "j"}, {"w", "q"},
{"w", "u"}, {"w", "v"}, {"x", "b"}, {"x", "f"}, {"x", "h"}, {"x",
"i"}, {"x", "j"}, {"x", "k"}, {"x", "l"}, {"x", "o"}, {"x", "p"},
{"x", "q"}, {"x", "r"}, {"x", "u"}, {"x", "v"}, {"x", "w"}, {"x",
"z"}, {"y", "j"}, {"y", "q"}, {"y", "v"}, {"y", "x"}, {"y", "z"},
{"z", "b"}, {"z", "f"}, {"z", "j"}, {"z", "q"}, {"z", "v"}, {"z",
"w"}, {"z", "x"}, {"z", "z"}}
In order to plot this information:
densityData =
delimCounts /. a_String :> Det@Position[Alphabet[], a];
ticks = Transpose[{Range[26], Alphabet[]}]
(Tuples[Alphabet[], {2}] //
Partition[#, Length@Alphabet[]] &) /.
{a_, b_} :> If[MemberQ[nolinks, {a, b}], Red, Black] //
ArrayPlot[#
, Frame -> True
, FrameTicks -> {{ticks, ticks}, {ticks, ticks}}
, FrameLabel -> {{Style["Word ends at", 16, Bold], None}
, {Style["Word starts at", 16, Bold], None}}
, DataReversed -> {True, False}
, PlotLabel ->
Style["Constraints on setting up a chain of English words", 24,
Bold]
, Mesh -> All
, MeshStyle -> {{Dotted, Gray}, {Dotted, Gray}}
, ImageSize -> 600
, Epilog -> {
{Text[Style[#3, 8, Yellow], {#2, #1} - 0.5] & @@@ densityData}
}
] &

The solid red columns at q
, j
and v
with near-zero counts mean that few words end there. A very small number of words start with x
, y
, z
and that can be seen from the corresponding rows. If one lands on one of the last three columns, they will find out that the opportunities to chain words using the top three rows are limited as well.
In the English language the higher counts in certain columns can be attributed in part to grammatical constructs. I don't have a definitive break down for this observation.
For example:
d
: Due to ed
endings for verbs in the past tense.
g
: Due to ing
endings for verbs in the present continuous tense
s
: Due to the plurals in the language (mostly)
r
: Due to comparative forms such as greener
y
: due to adverbs ending in -ly
As long as one does not run out of words at transposed locations, an arbitrary path can be chalked out by traversing the black squares. A simple example would be to start from q
and go to p
chaining and exhausting words, but since nothing ends in q
, one could turn back and go back to r
passing through the narrow bottleneck in the j
column (at most twice) and then getting stuck soon in the last three alphabet columns.
An example of a manual path that can find random words (with the exception of new words that end in j
, since there aren't many) is presented next.
manualpath = {{"q", "r"}, {"r", "s"}, {"s", "t"}, {"t", "u"}, {"u",
"g"}, {"g", "v"}, {"v", "w"}, {"w", "x"}, {"x", "y"}, {"y",
"a"}, {"a", "b"}, {"b", "c"}, {"c", "d"}, {"d", "e"}, {"e",
"f"}, {"f", "g"}, {"g", "h"}, {"h", "i"}, {"i", "h"}, {"h",
"j"}, {"j", "k"}, {"k", "l"}, {"l", "m"}, {"m", "n"}, {"n",
"o"}, {"o", "p"}};
If[Length@wordDelimitedBy[First@#, Last@#] >= 1,
RandomChoice@wordDelimitedBy[First@#, Last@#], {}] & /@ manualpath
{"quieter", "relights", "swankest", "trousseau", "unpromising",
"guv", "view", "wax", "xerography", "yoga", "alb", "bivouac",
"charred", "deflective", "engulf", "frightening", "gush", "hibachi",
"interfaith", "haj", "joystick", "karakul", "lam", "mouton",
"nympho", "outcrop"}
Any path that doesn't include tuples in nolinks
can be chosen as long as one doesn't run out of words with the chosen delimiters.