Beyond the basics, C has specifiers for wider types, and scanf in particular has some sharp edges to know about.
long distance = 1000000L;
long long veryBig = 9000000000LL;
size_t arraySize = 10;
printf("%ld\n", distance); // %ld for long
printf("%lld\n", veryBig); // %lld for long long
printf("%zu\n", arraySize); // %zu for size_t (unsigned, used for sizes/lengths)
double price;
scanf("%lf", &price); // scanf needs %lf for double — %f alone reads a float, corrupting memory
A classic mistake: printf uses %f for both float and double (because of automatic argument promotion when passing to a variadic function), but scanf requires the exact specifier — %f for float, %lf for double — because it writes directly into memory of a known size, and no promotion happens on the way in.
scanf("%s", buffer) is dangerous without a width limit: it copies input into buffer with no bounds checking, so long input overflows the buffer and corrupts adjacent memory. Prefer a width-limited version, scanf("%99s", buffer) for a 100-byte buffer, or better yet use fgets(buffer, sizeof(buffer), stdin) for reading whole lines safely. Always check scanf's return value too — it returns the number of items successfully matched, which lets you detect bad input instead of silently continuing with garbage.