The complexity of modern software systems is continuously growing, as well as the amount of data that is produced on a daily basis. This calls for sound and scalable approaches that can be used to tame such an emerging complexity. This tutorial aims at introducing the basic concepts of control theory that can be used to design self-adaptive systems. The tutorial is divided into two main parts. The first part discusses the modeling of discrete-time systems, and the design of controllers in the discrete-time domain. The second part presents a number of successful examples of the application of control-based approaches for the design of self-adaptive software systems.