The makefile can be confusing at times because a lot of the steps are left out for simplicity sakes, and implied by `make` using default behaviour. It might make a bit more sense if you manually replace the variables (OBJS and HDRS) with their "expanded" values, e.g.: # automatically generated list of object files # OBJS = $(SRCS:.c=.o) OBJS = speller.o dictionary.o # default target // here comes the confusing part! # $(EXE): $(OBJS) $(HDRS) Makefile # $(CC) $(CFLAGS) -o $@ $(OBJS) $(LIBS) speller: speller.o dictionary.o dictionary.h Makefile $(CC) $(CFLAGS) -o $@ speller.o dictionary.o # dependencies # $(OBJS): $(HDRS) Makefile speller.o dictionary.o: dictionary.h Makefile $(CC) $(CFLAGS) There are actually two different kinds of rules here: 1. When to compile something (e.g. `speller: speller.o dictionary.o dictionary.h Makefile`). 1. How to compile the thing (e.g. `$(CC) $(CFLAGS) -o $@ speller.o dictionary.o`). Your intuition is correct, in that `make` recompiles the `.c` files when the `.o` files are out of date. It's basically checking the timestamps, and if the `.c` file is newer than the corresponding `.o` file then `make` recompiles. The reason there is no direct correlation here is because `make` has a default behaviour to try to look for the `.c` file with the same name as the output file. Quick detour, recall earlier in the course where `make` was executed without the existence of a makefile, e.g: make mario Here you're asking `make` for an executable. `make` goes and figures out that it needs to find a file called `mario.c` file. Actually what's happening behind the scenes, is that `make` sees that it needs to create an executable, and to do that it needs a binary `.o` file. When `make` sees that a `.o` file is needed, it knows to look for a `.c` source file. The `.c` file is compiled to a `.o`, which is then linked to create the final executable. One way is to think of it as a chain of dependencies, that `make` fills in by itself, unless you tell it otherwise: mario -> mario.o -> mario.c Back to the makefile. When you run `make` from the command line, it will first look in the makefile for something to do. In this case it finds `speller` and tries to compile it. With the default behaviour, `make` would just look at `speller.c` before deciding if anything needs to be done. If we're only modifying `dictionary.c` then `make` would not see anything has changed, and would not do anything. The extra file names (`speller.o`, `dictionary.o`, etc) are telling `make` to look at those files as well, to see if anything has changed: speller: speller.o dictionary.o dictionary.h Makefile So if any of these files are newer than the current executable, then `make` knows to recompile using the line below that condition: $(CC) $(CFLAGS) -o $@ speller.o dictionary.o Without this line, `make` will just follow the default behaviour and compile `speller.o` only with the default options. The "dependencies" line is similar: speller.o dictionary.o: dictionary.h Makefile This is telling `make` that `speller.o` and `dictionary.o` should be recompiled if `dictionary.h` or `Makefile` has changes. The reason the `Makefile` itself is included in the list, is to ensure that `make` checks the `Makefile` for changes. This is to cater for the scenario where the `Makefile` changes, but not any of the source or binary files. The next line below that doesn't specify any compiler commands, so `make` will use the default behaviour to compile those files. The command it creates would look something like this: $(CC) $(CFLAGS) Since that is the command we want anyway it can be left out. `CC` and `CFLAGS` are both "default" variables in `make`. In other words, just by defining the `CFLAGS` variable set in the file, `make` will pass those arguments to the compiler.