The Rules

It's time to build a typing speed test in your language of choice!

1. You provide a file with a dictionary of choice (every 'word' in it must be newline delimited). Pipe it in via stdin or provide it's name as a command line argument.


2. Pick 10 random words from the file (no duplicates must be allowed) and print them out in the following manner:

-> direct
-> ground
-> next
-> five

3. Start measuring the time spent from now on!

4. Let the user type all of the ten words as fast as possible (ended with a carriage return). Print OK when you have a match, print WRONG when we have a typing mistake (or the word was already succesfully typed in this run).

5. Stop the clocks! Now print the CPM (Characters per minute) benchmark, which is calculated as follows: (sum of the characters of the chosen words / time spent typing (seconds)) * 60. Round to the nearest integer and reproduce the following (sample) output:

--> You have scored 344 CPM!

A sample run

-> settle
-> side
-> open
-> minister
-> risk
-> color
-> ship
-> same
-> size
-> sword
# ...................... some lines snipped ......................
--> You have scored 298 CPM!

The winner

This is code colf, the shortest entry (in source code character count) wins, have fun!

  • 5
    \$\begingroup\$ I think the winner should partly be scored by the person with the highest CPM ;) \$\endgroup\$
    – mellamokb
    May 8, 2012 at 19:55
  • \$\begingroup\$ How precisely do we need to measure the time? Is one second resolution OK? \$\endgroup\$ May 10, 2012 at 14:20
  • \$\begingroup\$ @Ilmari Karonen: a one second resolution would be fine for this specific contest. \$\endgroup\$ May 10, 2012 at 15:35

9 Answers 9


Bash - 217 212 199 196 chars

Not gonna win but it was fun

declare -A W
for w in `shuf -n10`;do C+=$w;echo -\> $w;W[$w]=OK;done
for((;${#W[*]};));do read r;echo ${W[$r]-WRONG};unset W[$r];done
echo --\> You have scored $((60*${#C}/SECONDS)) CPM!

Under 200 chars now!

Takes wordlist file as an argument Now takes word list on standard input. Paste it in the terminal and press ^D

Implemented suggestion from manatwork

  • 1
    \$\begingroup\$ Not gonna win but it was fun - I think the same when I post my C# solutions :) \$\endgroup\$ May 10, 2012 at 6:47
  • 1
    \$\begingroup\$ You could use the $SECONDS shell variable to simplify calculating the elapsed time. \$\endgroup\$
    – manatwork
    May 10, 2012 at 9:41
  • \$\begingroup\$ You can remove 2 more characters: 1) the : in the default value parameter expansion; 2) the $ in front of SECONDS in the arithmetic evaluation. Actually there is another extra character, the newline at the and of file. \$\endgroup\$
    – manatwork
    May 10, 2012 at 18:00

K, 146

Assumes a dictionary file called 'd' in the current working directory.

{b:+/#:'a:10?_0:`:d;-1"-> ",/:a;s:.z.t;while[#a;$[(,/0:0)~*a;[a:1_a;-1"OK"];-1"WRONG"]];-1"--> You have scored ",($(60000*b)%"i"$.z.t-s)," CPM!";}
  • \$\begingroup\$ Very nice and short, first entry in K I've seen (here on codegolf.se)... \$\endgroup\$ May 10, 2012 at 20:52
  • \$\begingroup\$ Thanks, all of my answers here are in Q or (increasingly) in K. \$\endgroup\$
    – tmartin
    May 11, 2012 at 9:05
  • \$\begingroup\$ I don't quite get why this only got a single upvote (mine)! \$\endgroup\$ May 17, 2012 at 20:08

Ruby (189 178 171 168)

$><<t=['',d=[*$<.lines].sample(10)]*'-> '
puts d.delete($stdin.gets)?:OK:'WRONG'while d[0]
puts'--> You have scored %i CPM!'%((t.size-40)/(Time.now-s)*60)

Pretty basic, I'm sure there are improvements to be made. Takes the filename of the dictionary as a command-line argument.

EDIT: A few minor tweaks, mainly around retaining the newlines from the dictionary. As a result the file will need a trailing newline to work correctly.


C, 305 309 347 chars

char*stdin,w[11][99];long i,t;main(int n,char**v){v=fopen(v[1],"r");
for(i=n=0;i<10;n+=printf("-> %s",w[++i])-4);
printf("--> You have scored %ld CPM!\n",n*60/(time(0)-t));}

Thanks to @ugoren for the improvement hints. Using an "11th word" to discard incoming dictionary entries was a big win over my previous strcpy-if-chosen approach.

Here's the ungolfed source:

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

static char words[11][99];
static long i, t;

int main(int argc, char *argv[])
    FILE *fp;
    int n;

    fp = fopen(argv[1], "r");
    for (i = 0 ; fgets(words[0], sizeof words[0], fp) ; ++i) {
        n = i < 10 ? i : rand() % i;
        if (n < 10)
            strcpy(words[n + 1], words[0]);

    n = 0;
    for (i = 1 ; i <= 10 ; ++i)
        n += printf("-> %s", words[i]) - 4;
    t = time(0);
    i = 1;
    while (i <= 10 && fgets(words[0], sizeof words[0], stdin)) {
        if (strcmp(words[0], words[i])) {
        } else {
    if (i > 9)
        printf("-> You have scored %ld CPM!\n", n * 60 / (time(0) - t));

    return argc - argc;
  • \$\begingroup\$ Some improvements: 1. K&R declaration: main(n,v)char**v;{.... 2. stdin can be char *. fgets(buf,len,stdin)=gets(buf) (never mind the buffer overruns). 3. What's wrong with rand()%i? RAND_MAX isn't needed. 4. Why long? \$\endgroup\$
    – ugoren
    May 9, 2012 at 13:51
  • 1
    \$\begingroup\$ Also: 1) t=time(0)->time(&t). 2) n*60/(time(0)-t) has parentheses that must go - *60 can be moved to n+=60*printf, then n/=time(0)-t. 3) Replace b with an extra element in w, replace strcpy with read directly into w. \$\endgroup\$
    – ugoren
    May 9, 2012 at 14:01
  • \$\begingroup\$ Replacing fgets() with gets() needs extra code to deal with the newlines in the dictionary; this wound up being shorter. rand()%i isn't sufficient; the actual calculation is (double)i*rand()/RAND_MAX. Moving the *60 to the printf also means changing the the -4 to -240 so it ultimately winds up a loss. Your other points are valid ones, though (I believe). Oh, and long is because time_t is traditionally a long. Just because we're golfing doesn't mean we can't be portable. \$\endgroup\$
    – breadbox
    May 9, 2012 at 16:55
  • \$\begingroup\$ I missed 4->240... Portability and golf don't go well together. But defining i,t; (implicitly int) is OK up to MAX_INT seconds (if you don't use time(&t)). With rand(), all you need is a 10/i chance, and rand()%i<10 does it. \$\endgroup\$
    – ugoren
    May 9, 2012 at 18:57
  • \$\begingroup\$ Your random selection is somewhat flawed (not that absolute fairness is required). The 11th line should have a 10/11 chance to be selected, but you do rand()%10>10, which gives it 100%. rand()%(i+1)>9 is better (but instead if %(i+1) do i++>9?. Also move *stdin to be first and save a space. \$\endgroup\$
    – ugoren
    May 13, 2012 at 11:25

Scala(319 306 304 302)

var s=util.Random.shuffle(io.Source.fromFile(args(0)).getLines.toSet)take 10
var l=(0/:s){_+_.size}
s map{"-> "+_}map?
def n=System.nanoTime
val t=n
while(s.size!=0){val m=readLine
if(s contains m)?("OK")else?("WRONG");s-=m}
?("--> You have scored "+l*60000000000L/(n-t)+" CPM!")

C# 401

void T(){
var w=new List<string>();
for(var l=R();l!="";l=R())w.Add(l);
var s=w.OrderBy(_=>Guid.NewGuid()).Take(10).ToList();
s.ForEach(x=>C("=> "+x));
var t=s.Select(x=>x.Length).Sum();
var c=Stopwatch.StartNew();
C("--> You have scored "+c.Elapsed.TotalSeconds*60/t+" CPM!");}

Running version here: http://ideone.com/Nt6Id


PHP 187 bytes

Newlines have been added for clarity:

--> You have scored <?=0|$l/(time()-$t)*60?> CPM!

Accepts the dictionary filename as a command line argument. The dictionary file must end with a newline.


Python (256 235)

import time as t,random as r
def p(x):print x
z=lambda x:p(("WRONG","OK")[raw_input()==x])or len(x)
p("--> "+"\n--> ".join(c))
p("--> You have scored %d CPM!"%(sum(map(z,c))/(t.time()-_)*60))

This is in python 2.x, in 3.x I can shave off 4 more characters by using the print function.

Newlines included

  • 2
    \$\begingroup\$ "Question: Do newlines count?" We usually go by "necessary characters". In the case of Python, yes, the newlines need to be counted, since removing them will keep the program from working. \$\endgroup\$
    – Joey Adams
    May 10, 2012 at 10:02
  • 1
    \$\begingroup\$ I think you can change z=lambda x: to def z(x):. \$\endgroup\$
    – Joey Adams
    May 10, 2012 at 10:04
  • \$\begingroup\$ @JoeyAdams: I could if only needed to return none, but I need to return len(x) and def z(x):return is 5 more charaters :/ \$\endgroup\$ May 10, 2012 at 14:12
  • \$\begingroup\$ I think you can win some characters by piping in the input (allowed in the question) and using input() instead of sys.argv[1].read() \$\endgroup\$ May 10, 2012 at 15:36
  • \$\begingroup\$ @JoelCornett: Whoops, you're right. \$\endgroup\$
    – Joey Adams
    May 10, 2012 at 16:49

[Wolfram Language (Mathematica)], 212 bytes

n=10;Print["-> "<>#]&/@(w=RandomWord@n);c=1;t=First@AbsoluteTiming[While[c<=n,Print@If[Echo@InputString[]==w[[c]],c++;"OK","WRONG"]]];Print["--> You have scored "<>ToString@⌊60Total@StringLength@w/t⌋<>" CPM!"]

Interesting challenge for WM.
Works only on desktop!
Using internal dictionary of common words instead of file.
Ungolfed version:

num = 10; Print["-> " <> #] & /@ (words = RandomWord@num);
size = Total@StringLength@words;
count = 1;
timing = First@AbsoluteTiming[
    While[count <= num,
      If[Echo@InputString[] == words[[count]],
       count++; "OK",
result = ToString@⌊60 size/timing⌋;
Print["--> You have scored " <> result <> " CPM!"]

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.