According to Rand Al'Thor's post in the Puzzling SE, a close-knit word is any word that contains three alphabetically consecutive letters (in any order).

Words like education, foghorn and cabaret are all considered close-knit words whereas words like learning, klaxon and perform are not close-knit words.


The challenge is to code-golf a program capable of taking a single word as input (assumed lower case, for all intents and purposes) and to return output that (if available) lists all consecutive letter sets (also in lower case) if it is a close-knit word, and empty output if it is not a close-knit word.


Input: education
Output: cde

Input: foghorn
Output: fgh

Input: cabaret
Output: abc

Input: hijacking
Output: ghi, hij, ijk

Input: pneumonia
Output: mno, nop

Input: klaxon
Output: <<no output>>

Input: perform
Output: <<no output>>

Input: learning
Output: <<no output>>


  1. Whereas input is to be assumed to be a single lower-case word and output must be lower-case, the nature of the output will vary according to the choice of your coding language. Please select a form of output that will best suit the nature of the challenge, whether it be STDOUT, file output, array, etc.
  2. Because this is code-golf, it will be a case of lowest number of bytes being the clear winner.
  3. No silly loopholes.
  4. I will not accept answers that have the consecutive letters in non-alphabetical order... So cab will not be deemed a suitable output for cabaret, for example.
  5. Special note, while the "triplets" don't necessarily have to be in alphabetical order, but the characters within the triplets must be... so in the case of the word "performance", for example, the output mno,nop will be accepted, as will nop,mno. In the case of the word "hijacking", there are six ways that the triplets of ghi, hij and ijk could be arranged in a list, and all six permutations are acceptable as output.

Other than that, on your marks, get set, golf!

  • \$\begingroup\$ Can the output be a 2D char array with each set of three consecutive letters in a column? \$\endgroup\$
    – Luis Mendo
    Commented Sep 19, 2016 at 23:11
  • \$\begingroup\$ @LuisMendo Can you give me an example just so that I can visualize it? \$\endgroup\$ Commented Sep 19, 2016 at 23:28
  • \$\begingroup\$ Try my code without the final ! And with another word, as the current one gives the same result :-) \$\endgroup\$
    – Luis Mendo
    Commented Sep 19, 2016 at 23:30
  • \$\begingroup\$ @LuisMendo is MATL column major or something? \$\endgroup\$
    – Maltysen
    Commented Sep 19, 2016 at 23:37
  • 1
    \$\begingroup\$ Is an output in the format of an array of tuples okay, i.e. the output for pneumonia can be [('m','n','o'),('n','o','p')])? \$\endgroup\$
    – R. Kap
    Commented Sep 20, 2016 at 23:44

26 Answers 26


Python 3.5, 68 bytes

while 1:s='%c'*3%(a,a+1,a+2);a+=1;{*s}-{*w}or print(s)

Prints output strings, and terminates with error when the character value gets too large.

Generates all strings of three consecutive and prints those that are a subset of the input word.


05AB1E, 7 6 5 bytes




3ã      # Cartesian product × 3 with input
  AΠ   # All substrings of the alphabet
    Ã   # Setwise intersection

Uses the CP-1252 encoding. Try it online!

  • \$\begingroup\$ This is just sheer genius... I'll have to try creating a challenge that pushes this language to the limit... ;) \$\endgroup\$ Commented Sep 21, 2016 at 0:13

Pyth - 11 10 8 7 bytes

Super brute force method.


Test Suite.

@            Setwise intersection, finds common strings between the two lists
 ^           Cartesian product
  z          Input
  3          Of length 3
 .:          Substrings. Without second input finds all substrings which is ok
  G          Lowercase aphabet

Jelly, 7 bytes


This is a monadic link. Try it online!

How it works

ØaẆfṗ3$  Monadic link. Argument: s (string)

Øa       Yield the lowercase alphabet, i.e., a := "abcdefghijklmnopqrstuvwxyz".
  Ẇ      Window; yields all substrings of a.
      $  Combine the two links to the left into a monadic chain.
    ṗ3   Take the third Cartesian power of s, yielding all combinations of three
         characters that can be formed from the letters in s.
   f     Filter; keep only those substrings of a that appear in the Cart. power.

JavaScript (ES6), 95 90 bytes

<input oninput="o.textContent=f(this.value).join`, `"><div id=o>

Missing values concatenate as undefined, so the resulting string contains greater than 3 characters. I borrowed the !c[3] trick from @ETHproductions to save 5 bytes.

  • 2
    \$\begingroup\$ Coincidentally enough undefined is a close-knit word ;) \$\endgroup\$ Commented Sep 19, 2016 at 23:43
  • \$\begingroup\$ Why parseInt(c,36) instead of c.charCodeAt()? \$\endgroup\$
    – Titus
    Commented Sep 20, 2016 at 0:51
  • \$\begingroup\$ @Titus I guess it makes no difference, I'm just used to using parseInt in code golf. \$\endgroup\$
    – Neil
    Commented Sep 20, 2016 at 7:56

Haskell, 48 bytes

f w=filter(all(`elem`w))[take 3[c..]|c<-['a'..]]

Generates all triples of three consecutive characters, takes those that use only letters in the input.

53 bytes:

f w=filter(all(`elem`w))[[pred$pred c..c]|c<-['c'..]]

The list ['c'..] contains all unicode characters from 'c' onward. The list comprehension [[pred$pred c..c]|c<-['c'..]] turns them into all strings of 3 consecutive characters from "abc" onward. We step backwards with [pred$pred c..c] instead of forwards with [c..succ$succ c] to avoid an error when taking the successor of the highest unicode character.

These triples are filtered for those that only use letters in the input.

  • \$\begingroup\$ Great answer! I tried making it pointfree, but alas, it's still 48 bytes \$\endgroup\$
    – user
    Commented Jan 17, 2021 at 22:05

Python 3.5, 78 bytes

for c in s:o=ord(c);a=chr(o-2);b=chr(o-1);s>{a,b}and print(a+b+c)

PHP, 100 bytes


takes input as command line argument; prints a trailing comma. run with -r.

  • 1
    \$\begingroup\$ for($s=join(range(Z,z));$a=substr($s,++$i,3);) is a shorter way of creating $a. It admittedly checks a bunch of punctuation and also some 2-character series but as the input is lower case letters only and it requires that it finds 3 characters that's fine. \$\endgroup\$
    – user59178
    Commented Sep 20, 2016 at 13:33
  • \$\begingroup\$ for($a="ab",$i=98;$i<123;)!strstr(count_chars($argv[1],3),$a=substr($a,1).chr(++$i))?:print"$a,";` saves 2 Bytes. Very nice way, I have tried other ways in PHP but can't reach the Bytes of your code. I'm not sure if you need a space after the comma \$\endgroup\$ Commented Sep 20, 2016 at 17:02

C, 175 174 bytes

main(_,a,m,n)char**a;{char*s=a[1],*t=s;while(*++s)while(s>t&&(n=s[-1])>=*s){m=(*s^n)?*s:0;*s=n;*--s=m;!*t&&++t;}for(;t[1]&t[2];++t)*t==t[1]-1&&*t==t[2]-2&&printf("%.3s ",t);}


    *t==t[1]-1&&*t==t[2]-2&&printf("%.3s ",t);

While doing the sort it replaces duplicate values with 0s, these 0s get sorted to the beginning of the word. Looking for the consecutive values is then trivial.

  • 1
    \$\begingroup\$ Welcome to the site! \$\endgroup\$
    – DJMcMayhem
    Commented Sep 21, 2016 at 17:28
  • 1
    \$\begingroup\$ Yes, welcome to PPCG! Great work on your answer, C is not one of the easiest to golf in! \$\endgroup\$ Commented Sep 21, 2016 at 20:20

MATL, 13 bytes


Try it online!

2Y2    % Push string of lowercase alphabet
3YC    % 2D char array with sliding blocks of size 3, each on a column
t      % Duplicate
j      % Take input
m      % Member function: true for elements of the 2D array that are in the input
A      % All: true for columns that consist of all true values
Z)     % Use as logical index into the columns of the 2D char array
!      % Transpose. Implicitly display

Perl, 36 bytes

Includes +1 for -n

Give input on STDIN:

perl -nE 'join("",a..z)=~/[$_]{3}(?{say$&})^/' <<< "hijacking"

Just the code:


T-SQL, 153 bytes

Had to react on the comment from WallyWest, about it being long time since last TSQL answer. Answer was partly inspired by Brian J's answer


DECLARE @ varchar(max)='hijacking'

;WITH C as(SELECT distinct ascii(substring(@,number,1))z FROM spt_values)SELECT CHAR(C.z)+CHAR(D.z)+CHAR(E.z)FROM C,C D,C E WHERE c.z+1=d.z and d.z=e.z-1



USE MASTER -- can be left out if the master database is already being used
DECLARE @ varchar(max)='hijacking'

;WITH C as
  SELECT distinct ascii(substring(@,number,1))z
  FROM spt_values
WHERE c.z+1=d.z and d.z=e.z-1
  • 1
    \$\begingroup\$ That's clever! Didn't even know about that table. Good thing there aren't words more than 2048 letters long! \$\endgroup\$
    – Brian J
    Commented Sep 21, 2016 at 14:41

Haskell, 63 60 52 bytes

f w=[x|x<-take 3<$>scanr(:)"_"['a'..],all(`elem`w)x]

Usage example: f "hijacking" -> ["ghi","hij","ijk"].

scanr(:)"_"['a'..] builds a list with the tails of the list of all unicode chars starting with 'a' and lets it end with a '_', i.e. ["abcde...\1114111_", "bcde...\1114111_", "cde...\1114111_", ..., "\1114109\1114110\1114111_", "\1114110\1114111_", "\1114111_", "_"]. Then we take up to three chars of each string and bind it to x. Keep all x where every letter of it is in the input parameter w.

Edit: @xnor saved 3 7 bytes. Thanks!

  • \$\begingroup\$ Does anyone know if I can capture the first three elements of the list a:b:c:_ with an @-pattern? \$\endgroup\$
    – nimi
    Commented Sep 19, 2016 at 23:09
  • \$\begingroup\$ I don't know about the @-pattern, but you can remove the 'z' upper bound and just let it try all characters. \$\endgroup\$
    – xnor
    Commented Sep 19, 2016 at 23:13
  • \$\begingroup\$ Capturing those first 3 elements is really annoying. Best I can see is just to use take and remove the empty string: f w=[x|x<-init$take 3<$>scanr(:)""['a'..],all(`elem`w)x] \$\endgroup\$
    – xnor
    Commented Sep 19, 2016 at 23:38
  • \$\begingroup\$ @xnor: nice. we can start the scanr with "." instead of "" and omit the init$. \$\endgroup\$
    – nimi
    Commented Sep 19, 2016 at 23:49

T-SQL (SQL Server 2014), 217 bytes


declare @ table(a char)declare @i int=1while @i<=len(@a)begin insert into @ values(SUBSTRING(@a,@i,1))set @i+=1 end select distinct t.a+u.a+v.a from @ t,@ u,@ v where ASCII(t.a)+1=ASCII(u.a)and ASCII(u.a)+1=ASCII(v.a)


First declare the variable @a as a char of some sort and assign the input like so

declare @a varchar(max) = 'pneumoultramicroscopicsilicovolcanoconiosis'

I didn't count the declare as part of my code, but I didn't find a sql standard for input, so I'm willing to change my counting

Output will either be one row for each triple, or no rows if the word is not close knit


declare @temp table ( letter char(1) ) -- table to hold each letter of the word

declare @i int = 1

while @i <= len(@input) -- split each letter, and each row in @temp will have one letter
    insert into @temp values (SUBSTRING(@input, @i, 1))
    set @i = @i + 1

-- join the letters table to itself to get three letter triples, where the three are in adjacent increasing order
-- use distinct because there might be duplicates in the word
select distinct t1.letter + t2.letter + t3.letter
from @temp t1
cross apply @temp t2
cross apply @temp t3
where ASCII(t1.letter) + 1 = ASCII(t2.letter)
and ASCII(t2.letter) + 1 = ASCII(t3.letter)
  • \$\begingroup\$ the declaration wouldn't be counted seeing we're dealing with the code that would be required to execute the required functionality post-declaration in this instance. Great work, been a while since I've seen an SQL solution for a challenge. Great work! \$\endgroup\$ Commented Sep 20, 2016 at 23:33
  • \$\begingroup\$ I have golfed your script down to 185 characters, here is the ungolfed version. You may want to check out my answer as well \$\endgroup\$ Commented Sep 21, 2016 at 11:04

R, 220 bytes

My solution is pretty straightforward. It loops through the possible three letter combinations, loops through and checks the characters of the entered string against the three consecutive letters, and adds them to a string. The string is then only printed when three letters are found (c==4).

f<-function(w){if(nchar(w)>2){for(i in 1:24){
for(k in 1:3){for(j in 1:nchar(w)){if(substr(w,j,j)==intToUtf8(95+k+i)&c<4){


> f("education")
> [1] "cde"
> > f("foghorn")
> [1] "fgh"
> > f("cabaret")
> [1] "abc"
> > f("hijacking")
> [1] "ghi"
> [1] "hij"
> [1] "ijk"
> > f("pneumonia")
> [1] "mno"
> [1] "nop"
> > f("klaxon")
> > f("perform")
> > f("learning")
> > 

Python 3.5, 114 111 88 80 79 bytes:

lambda X,W=[*map(chr,range(65,91))]:[i*({*X}>={*i})for i in zip(W,W[1:],W[2:])]

An anonymous lambda function. Takes input as an uppercase string and outputs an array of tuples, with the ones filled with three uppercase characters representing all sets of 3 consecutive letters that appear in the input. For example,

[(), (), (), (), (), (), ('G', 'H', 'I'), ('H', 'I', 'J'), ('I', 'J', 'K'), (), (), (), (), (), (), (), (), (), (), (), (), (), ()]

would be the output for input HIJACKING. This output format has been confirmed to be okay by OP. So has the only uppercase input format. However, if you want to input in only lowercase, simply replace range(65,91) with range(97,123), adding one more byte.

Repl.it with all Test Cases!


Basically what is happening here is:

  1. A list, W, is created using W=[*map(chr,range(65,91))], which contains all the uppercase letters in the English alphabet. Because of this, an uppercase input is always required.

  2. For each tuple, i, in a list, which we will call U, containing all three consecutive letter tuples, i.e.:


    created by zip(W,W[1:],W[2:]), each i is fully added to the output list as long as all elements in the set version of i ({*i}) are in the set version of input X ({*X}), i.e. {*X}>={*i}, i.e. X is a superset of i. Otherwise, the empty version of i (()) is added to the list.

  3. Once all the tuples have been gone through with the matches fully added, the list is returned as the final output.


Scala, 59 bytes

(s:Set[Char])=>'a'to'z'sliding 3 filter{_.toSet subsetOf s}


(s:Set[Char]) => ('a' to 'z').sliding(3).filter{threeChars => threeChars.toSet.subsetOf(s)}


(s:Set[Char])=>             //define a function with a Set of Chars called s as an argument
'a' to 'z'                  //create a Range of characters 'a' to 'z'
sliding 3                   //create an Iterator(Seq(a, b, c), Seq(b, c, d), Seq(c, d, e), ... , Seq(x, y, z))
filter{_.toSet subSetOf s}  //keep only the triplets which are a subset of s

Actually, 13 bytes

Golfing suggestions welcome. Try it online!



                Implicit input string s.
S               sorted(s).
 3@╧            Push all length-3 combinations of s.
    `εj`M       Join all of those combinations into single strings.
         3úV    Push all slices of the lowercase alphabet of length 1 <= n <= b
            ∩   Push the intersection of the combinations and slices.
                Implicit return.

Java 7, 230 bytes

String c(char[]q){java.util.Arrays.sort(q);char a[]=new String(q).replaceAll("(.)\\1","$1").toCharArray(),c=97,i=2;String r="",z="",s;for(;c<'z';z+=c++);while(i<a.length)if(z.contains(s=""+a[i-2]+a[i-1]+a[i++]))r+=s+" ";return r;}

This can most likely be golfed, but the challenge was a lot tougher than I originally thought in Java..

Ungolfed & test cases:

Try it here.

class M{
  static String c(char[] q){
    char a[] = new String(q).replaceAll("(.)\\1", "$1").toCharArray(),
         c = 97,
         i = 2;
    String r = "",
           z = "",
    for(; c < 'z'; z += c++);
    while(i < a.length){
      if(z.contains(s = "" + a[i-2] + a[i-1] + a[i++])){
        r += s+" ";
    return r;

  public static void main(String[] a){


ghi hij ijk 
mno nop 

abc bcd cde def efg fgh ghi hij ijk jkl klm 
  • \$\begingroup\$ Just have to ask why Java? It isn't the most golfable of languages...? +1 for effort of course... \$\endgroup\$ Commented Sep 20, 2016 at 9:34
  • 1
    \$\begingroup\$ @WallyWest Well, I'm a Java developer in everyday life. And I know I will never win any challenge with how verbose Java is, but it's still fun to codegolf in Java imho. :) \$\endgroup\$ Commented Sep 20, 2016 at 9:41
  • 1
    \$\begingroup\$ guess ill have to come up with a few creative code challenges in the near future for you to participate :) good work nevertheless! \$\endgroup\$ Commented Sep 20, 2016 at 9:46

PowerShell v2+, 93 bytes

param($n)97..120|%{-join[char[]]($_,++$_,++$_)}|?{(-join([char[]]$n|sort|select -u))-match$_}

Feels much longer than required, but I can't seem to golf it any further.

Takes input $n. Loops from 97 to 120, constructing contiguous three-letter strings -- that is, up to the |?, we'll have abc, bcd, cde, etc. on the pipeline. Then that's fed through a Where-Object (the |?) to pull out only those items where the clause is true. Here, the clause is 1) the input string $n, cast as a char-array, sorted and select -unique'd, then -joined back into a string, 2) -matched against the three-letter strings (i.e., regex matching). If it's a match, then the three-letter string is in the word, and so it filters through the |?. The results are left on the pipeline and output is implicit.


(Note that here the output is space-separated, since we're stringifying the output by concatenation.)

PS C:\Tools\Scripts\golfing> 'education','foghorn','cabaret','hijacking','pneumonia','klaxon','perform','learning'|%{"$_ -> "+(.\close-knit-words.ps1 $_)}
education -> cde
foghorn -> fgh
cabaret -> abc
hijacking -> ghi hij ijk
pneumonia -> mno nop
klaxon -> 
perform -> 
learning -> 
  • \$\begingroup\$ Great explanation. I'd give you two up votes if possible. \$\endgroup\$ Commented Sep 20, 2016 at 13:31

Retina, 106 56 bytes


Deduplicate, Sort. Add abc. Find if the substring is found and append if so. Translate to next substring. Repeat. Then remove first two lines.

Try it online

Naive solution:


Deduplicate, Sort, then output overlapping matches of 3 sequential letters.

Try it online

  • \$\begingroup\$ The naïve solution looks pretty straightforward... though I like your golfed solution better... well done! \$\endgroup\$ Commented Sep 20, 2016 at 23:29

JavaScript (Firefox 48), 93 bytes

x=>[for(c of a=[...new Set(x,i=0)].sort())if(parseInt(d=c+a[++i]+a[i+1],36)%1333==38&!d[3])d]

This lends itself to a 96-byte ES6 version:

x=>[...new Set(x)].sort().map((c,i,a)=>c+a[i+1]+a[i+2]).filter(x=>!x[3]&parseInt(x,36)%1333==38)

How it works

The first major chunk of the function is this:

[...new Set(x)].sort()

new Set(string) creates a Set object that contains one of each unique character in the string. For example, new Set("foghorn") will return Set ["f", "o", "g", "h", "r", "n"]. We can convert this into an array with [... ], then sort it with the built-in .sort(). This turns "foghorn" into ["f", "g", "h", "n", "o", "r"].

The next step is this:


This maps each character in the array to the character concatenated with the two items after it. For example, ["f", "g", "h", "n", "o", "r"] => ["fgh", "ghn", "hno", "nor", "orundefined", "rundefinedundefined"]. (The undefineds pop up when you try to access a non-existent member of the array.

The final step is filtering:


First, the !c[3]& is to rule out any strings that contain undefined. This is necessary because a bug causes the following algorithm to count e.g. gmundefined as a consecutive triplet.

All three-consecutive-char strings, when interpreted as base-36 numbers, are 38 modulo 1333. I figured this out by the following calculation:

  • 012 (base 36) = 38
  • 123 (base 36) = 1371
  • 1371 - 38 = 1333
  • 1371 mod 1333 ≡ 38 mod 1333 ≡ 38

Therefore, if a three-char string is 38 mod 1333 in base-36, the three characters are consecutive in the alphabet.

Test snippet

function test(x){O.innerHTML=/[^a-z]/.test(x)?"Invalid input":f(x);}
f=x=>[...new Set(x)].sort().map((c,i,a)=>c+a[i+1]+a[i+2]).filter(x=>!x[3]&parseInt(x,36)%1333==38)
<input id=I value="hijacked">
<button onclick="test(I.value)">Run</button>
<pre id=O>cde,hij,ijk</pre>

  • \$\begingroup\$ This fails for words such as gem and mage. \$\endgroup\$
    – Neil
    Commented Sep 20, 2016 at 23:55
  • \$\begingroup\$ So you're saying that all consecutive letter triplets when converted back from hexatrigesimal (base 36) are 38 when mod'd against 1333... that is freaking awesome! \$\endgroup\$ Commented Sep 20, 2016 at 23:55
  • \$\begingroup\$ @Neil Fixed at the cost of six bytes. \$\endgroup\$ Commented Sep 21, 2016 at 0:06
  • \$\begingroup\$ I appropriated your !c[3] trick which brought down my ES6 answer to the length of your previous ES6 answer, so now I'm even outgolfing your Firefox 30+ answer. Sorry about that. \$\endgroup\$
    – Neil
    Commented Sep 21, 2016 at 8:26
  • \$\begingroup\$ @Neil I don't mind :) \$\endgroup\$ Commented Sep 21, 2016 at 13:19

Racket 237 bytes

(define(f s)(let((lr(λ(l i)(list-ref l i)))(l(sort(map char->integer(string->list s))<)))(for((i(-(length l)2)))
(when(=(-(lr l(+ i 2))(lr l(+ i 1)))1(-(lr l(+ i 1))(lr l i)))(for((j 3))(display(string(integer->char(lr l(+ i j))))))))))


(f "education")



Detailed version:

(define(f2 s)
  (let ((lr (λ(l i)(list-ref l i)))
        (l (sort (map char->integer (string->list s)) <)))
  (for ((i (-(length l)2)))
    (when (=  (- (lr l (+ i 2)) (lr l (+ i 1)))
              (- (lr l (+ i 1)) (lr l i)))
      (for((j 3))
        (display (string(integer->char (lr l (+ i j))))))))))

Ruby, 50 bytes

each_cons(3) gets all consecutive sublists of length 3 from the alphabet ?a..?z, then use e&s.chars==e to select only the ones that have all characters in the target string by using setwise intersection. Returns a list of lists.


Try it online!


[R], 110 bytes


I'm sure its still golfable


Japt, 10 9 bytes

;Cã3 fUá3

Try it or run all test cases

;Cã3 fUá3     :Implicit input of string U
;C            :Lowercase alphabet
  ã3          :Substrings of length 3
     f        :Filter elements contained in
      Uá3     :Permutations of U of length 3

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.