A strong answer
A multi-file C++ program has three layers: headers (declarations), implementation files (definitions), and a build script (compile + link).
Header (contact.h): Contains the Contact struct definition and function declarations (parse_contact, load_contacts, save_contacts, to_csv). Wrap in header guards (#ifndef CONTACT_H ... #endif). Every .cpp that needs the type includes this header — they all see the exact same definition.
Implementation files (parse.cpp, io.cpp, main.cpp): Each includes contact.h and defines its functions. parse.cpp has the CSV parser. io.cpp has file load/save. main.cpp has the interactive loop. None of them defines Contact — they all get it from the header.
Build script: Compile each .cpp to an object file: g++ -std=c++17 -c parse.cpp -o parse.o (repeat for io.cpp, main.cpp). Then link: g++ parse.o io.o main.o -o contact_book. The linker resolves load_contacts from io.o, parse_contact from parse.o, and main from main.o.
One Definition Rule: The Contact struct is defined once in the header. Because every translation unit includes the header, they all agree on its layout. If you accidentally defined Contact in two .cpp files with different members, the linker would not catch it (each TU compiles separately) but the program would have undefined behavior. Header guards prevent double inclusion within one TU; the convention of define in header, declare in header, implement in one .cpp prevents cross-TU conflicts.
Incremental builds: The script above recompiles everything. A real build system (make, ninja, CMake) tracks timestamps: if parse.cpp changes, only parse.cpp recompiles; the link step always runs because the final executable depends on all objects.
What the interviewer is listening for
- Header guards and why they matter.
- Compile-then-link:
.cpp→.o(compile) → executable (link). - One Definition Rule: one definition of each entity across the whole program.
- Why
Contactgoes in the header (shared layout) while function bodies go in one.cpp(single definition). - How the linker resolves symbols across object files.
- Incremental build concept: timestamps → dependency graph → minimal recompile.
Common mistakes
- Putting function definitions (not just declarations) in the header without
inlineorstatic— causes multiple-definition linker errors when included from multiple.cppfiles. - Forgetting header guards → double inclusion breaks compilation.
- Defining the same function in two
.cppfiles (e.g.,parse_contactin bothparse.cppandmain.cpp) → linkermultiple definition. - Not linking all object files —
undefined referenceat link time. - Assuming the compiler sees all files at once — it compiles each TU independently.