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).