After a few computer science courses, students may start to 
get the feeling that programs can always be written to 
solve any computational problem.  Writing the program may 
be hard work.  For example, it may involve learning a 
difficult technique.  And many hours of debugging.  But 
with enough time and effort, the program can be written.  

So it may come as a surprise that this is not the case: 
there are computational problems for which no program 
exists.  And these are not ill-defined problems (Can a 
computer fall in love?) or uninteresting toy problems.  
These are precisely defined problems with important 
practical applications.

Theoretical computer science can be briefly described as 
the mathematical study of computation.  These notes will 
introduce you to this branch of computer science by 
focusing on computability theory and automata theory.  You 
will learn how to precisely define what computation is and 
why certain computational problems cannot be solved.  You 
will also learn several concepts and techniques that have 
important applications.  Chapter 1 provides a more detailed 
introduction to this rich and beautiful area of computer 
science.  

These notes were written for the course CS345 Automata 
Theory and Formal Languages taught at Clarkson University.  
The course is also listed as MA345 and CS541.  The 
prerequisites are CS142 (a second course in programming) 
and MA211 (a course on discrete mathematics in which 
students also gain experience writing mathematical proofs).