These days, many networked programs spend their time not computing, but waiting for responses from remote machines. An efficient way to implement such a program is called asynchronous I/O, or "async". I demonstrate how to implement a basic async framework in Python 3. It uses "select", non-blocking sockets, and an event loop. Success! We can fetch two URLs concurrently, even though our program is single-threaded. Unfortunately, all the logic is in callback functions, and this will become unmanageable if our program grows more complex. Coroutines are a better alternative to callbacks. To understand how they work, we look at an example of coroutine code in action. It is a Python generator, but what does that mean? We explore the Python interpreter’s generator implementation, how it uses a stack frame and instruction pointer in an unorthodox way to pause and resume the generator at will. Coroutines build upon generators: they can pause waiting for a network operation, and resume when the operation completes. I show a minimal implementation of the Future and Task classes used in asyncio to schedule coroutines. Abracadabra! In half an hour we built an async framework with coroutines that can fetch two URLs concurrently, and the code is as legible as a traditional implementation is.