AlgoInsight

Q&A

What is the #if directive in C?

By AlgoInsight Team2026-06-18c

Overview

C's preprocessor transforms source text before compilation. #if/#ifdef/#ifndef/#elif/#else/#endif let you selectively include code based on conditions evaluated entirely at preprocessing time. The compiler never sees the excluded branches - they're as if they don't exist in the translation unit. This is distinct from runtime if - the decision is made when you compile, not when you run.

graph TD
    A["#if CONDITION"] --> B{Condition true at compile time?}
    B -->|yes| C["Included in compiled code"]
    B -->|no| D["Stripped out before compiler sees it"]
    C & D --> E["#endif - end of conditional block"]
    style C fill:#1e1b4b,color:#fff

Basic #if / #elif / #else / #endif

#define VERSION 2

#if VERSION == 1
    printf("Running v1\n");
#elif VERSION == 2
    printf("Running v2\n");
#else
    printf("Unknown version\n");
#endif

The preprocessor evaluates the expression; only the matching branch is compiled. Expressions can use ==, !=, <, >, &&, ||, ! with integer constants and macros.

#ifdef and #ifndef (existence checks)

#ifdef DEBUG
    printf("Debug value: %d\n", x);   // only compiled when DEBUG is defined
#endif

// Equivalent to:
#if defined(DEBUG)
    // ...
#endif

#ifdef MACRO - true if MACRO has been #defined (regardless of its value). #ifndef MACRO - true if MACRO has NOT been defined.

Most Common Use 1: Include Guards (prevent double inclusion)

// myheader.h
#ifndef MYHEADER_H
#define MYHEADER_H

// ... header contents ...

#endif

The first time this header is included, MYHEADER_H is undefined → contents compiled → macro defined. Any subsequent #include of the same header finds MYHEADER_H already defined → contents skipped. This prevents redefinition errors. (Modern equivalent: #pragma once, widely supported.)

Most Common Use 2: Platform-Specific Code

#if defined(_WIN32)
    #include <windows.h>
    void sleep_ms(int ms) { Sleep(ms); }
#elif defined(__APPLE__)
    #include <unistd.h>
    void sleep_ms(int ms) { usleep(ms * 1000); }
#else
    #include <unistd.h>
    void sleep_ms(int ms) { usleep(ms * 1000); }
#endif

Each platform gets the right implementation; the others don't reach the compiler.

Most Common Use 3: Debug vs Release Builds

#ifdef DEBUG
    #define LOG(msg) fprintf(stderr, "[DEBUG] %s:%d: %s\n", __FILE__, __LINE__, msg)
#else
    #define LOG(msg)   // empty macro in release - zero overhead
#endif

Compile with -DDEBUG (gcc/clang) to enable logging; production builds omit it entirely.

defined() Operator

#if defined(WINDOWS) && !defined(LEGACY_MODE)
    // modern Windows code
#endif

defined(MACRO) is more flexible than #ifdef because it can be combined with other conditions in a single #if.

The Difference: #if vs runtime if

#if (preprocessor) if (runtime)
When Before compilation At program execution
Expression Integer constants, macros Any expression
Excluded code Never compiled Always compiled, not executed
Performance Zero runtime cost Branch prediction, code size
Debug build overhead Zero Possible

Why This Matters in Interviews

C/C++ systems interviews use #if to test preprocessor knowledge. Key points: #if is evaluated at compile time (by the preprocessor, before the compiler); #ifdef/#ifndef check macro existence; the three major use cases are include guards, platform-specific code, and debug/release builds; and the conditional branches that don't match are not compiled at all (true dead-code elimination). Distinguishing it from runtime if is the core conceptual point.

Common Pitfalls

References

More answers