Interview questions

C dependency injection interview: 30-second answer with C and C++ examples

August 5, 2025Updated May 20, 202618 min read
Can c dependency injection Be the Secret Weapon for Acing Your Next Interview

A plain-English guide to the C dependency injection interview: the 30-second answer, a 60-second version, C and C++ examples, lifetime pitfalls, and the.

You know the term. You've seen it in code reviews, architecture docs, maybe a few Stack Overflow threads. But when an interviewer asks you to explain it out loud, something happens — the words scatter. A C dependency injection interview catches a lot of mid-level engineers exactly there: they understand the concept well enough to use it, but they haven't built the sentence that survives live pressure.

That sentence exists, and it's short. This article gives you the 30-second version, the 60-second version, a C++ example you can sketch on a whiteboard, and a plain-C version that works even when you're talking to someone building firmware. It also covers the traps: confusing DI with inversion of control, reaching for a service locator and not realizing you did, and describing lifetimes in ways that signal you've never actually debugged a dangling reference.

Say the definition before you say the acronym

What dependency injection actually means in plain English

Dependency injection in C — or in any language — means a module gets the things it needs from the outside instead of creating them itself. That's it. The module does not call `new`, does not reach for a global, does not call a factory hidden inside its own constructor. Someone else builds the dependency and hands it in.

The reason this definition is worth memorizing verbatim is that interviewers often interrupt early. If your first sentence is "well, it's part of the SOLID principles and relates to the dependency inversion principle," you've already lost the thread. Start with the behavior: the module receives its dependencies rather than constructing them.

According to Martin Fowler's foundational writing on the subject at martinfowler.com, the core idea is separating configuration from use — the object that uses a service should not be responsible for locating or building it.

What this looks like in practice

Imagine a `DataLogger` function that needs a clock to timestamp entries. The hard-coded version calls `time(NULL)` directly inside the function. That works fine until you want to test it — now your test is coupled to the system clock, and you can't control what time it thinks it is.

The injected version takes a function pointer or a struct with a `get_time` field. In the test, you pass a fake that always returns noon on a fixed date. The `DataLogger` doesn't know or care. That's the whole game: the dependency is a parameter, not a secret.

This definition has been tested in timed mock sessions. The spoken version takes about eight seconds. That matters because the first eight seconds of an answer set the frame for everything that follows.

Memorize the 30-second answer, then stretch it to a minute

The 30-second version

Here is a script you can actually memorize for a dependency injection interview:

"Dependency injection means a module receives its dependencies from the outside instead of creating them itself. In C++, that usually means passing a collaborator into the constructor. In plain C, you pass a struct of function pointers or a context pointer. The benefit is that you can swap the real implementation for a test double without touching the module being tested."

Say that out loud right now. It runs about 25 seconds at a normal speaking pace. It defines the concept, names the mechanism in both languages, and names one concrete benefit. That's enough for an interviewer who wants a quick calibration.

The 60-second version

"Dependency injection means a module receives its dependencies from the outside rather than constructing them itself. The module declares what it needs — a logger, a clock, a transport — and whoever builds the module provides those things. In C++, the cleanest way to do this is constructor injection: the dependencies go in as constructor arguments, and the object can't be built without them. In plain C, you do the same thing manually — you pass a struct of function pointers or a context struct into the module's init function.

The reason this matters is testability. If the module creates its own dependencies, you can't replace them. If they're injected, you swap in a fake for testing, a mock for validation, or a different real implementation for a different platform. It also makes ownership clearer — you can see exactly where the dependency is created and how long it lives."

What this looks like in practice

Both versions answer the same prompt: "What is dependency injection?" The 30-second version is the floor. If the interviewer nods and moves on, you're done. If they lean in — "can you say more about the testability piece?" — you have the 60-second version ready to continue without restarting from scratch.

Google's testing blog has documented extensively why injecting dependencies rather than constructing them internally is the foundation of testable code. The substitutability argument — that you can only mock what you can replace — is the one interviewers most often follow up on.

Use constructor injection in C++ because it's the cleanest interview answer

Why constructor injection is the example interviewers expect

Constructor injection in C++ makes dependencies visible at the call site. When someone reads `Engine e(logger, clock);`, they know immediately that `Engine` needs a logger and a clock. Nothing is hidden. Compare that to an object that calls `Logger::getInstance()` inside a method — the dependency exists, but you can't see it from the outside, can't replace it, and can't test around it.

Interviewers asking about dependency injection in C++ are often filtering for this exact distinction: can you tell the difference between a dependency that's declared and one that's hidden?

What this looks like in practice

In a test, you create a `FakeLogger` that implements `ILogger` and captures messages. You pass it in. The `Engine` never knows it's talking to a fake. In production, you pass the real logger. The wiring happens at the composition root — wherever `main` or your factory builds the object graph — not inside `Engine` itself.

Why the example matters for ownership and lifetime

C++ candidates who understand DI but haven't shipped with it often stumble on one question: who owns the injected object? In the example above, `Engine` holds a reference, not a pointer, and definitely not ownership. The caller creates the logger, passes it in, and is responsible for keeping it alive as long as the `Engine` exists.

This is the part interviewers are actually probing when they ask follow-ups. If the logger is destroyed before the engine, you have a dangling reference. If the engine outlives a scoped logger, you have undefined behavior. Showing that you understand this — that injection doesn't transfer ownership by default — is what separates a candidate who read about DI from one who has actually used it.

Do the same thing in plain C without pretending you have a container

Why plain C DI is mostly manual wiring

C doesn't have constructors, interfaces, or containers. What it has is structs, function pointers, and explicit initialization. That's enough. Dependency injection in C is just the same idea expressed with those tools: you define what the module needs, and you pass it in at init time.

The mistake candidates make is assuming DI requires a framework. It doesn't. It requires a deliberate choice not to reach for globals.

What this looks like in practice

In a test, you fill `SensorDriverDeps` with a fake `log` that writes to a buffer and a fake `get_time_ms` that returns a fixed value. You pass that struct into `sensor_driver_init`. The driver is fully exercised without touching real hardware.

Why embedded candidates should care

In embedded systems, the constraints are different: no heap allocation, predictable teardown, deterministic timing. A runtime container that allocates objects dynamically is a non-starter on many platforms. Manual wiring — where the dependency structs are statically allocated and passed in at startup — fits those constraints perfectly.

The struct-of-function-pointers pattern is common in embedded codebases for exactly this reason. You get the testability benefits of DI without any runtime overhead. The James Grenning embedded TDD resources cover this pattern in detail, including how to build test seams in plain C without modifying production code.

Don't mix up DI, inversion of control, and the service locator pattern

DI is a technique, IoC is the bigger idea

The DI vs inversion of control distinction trips up more candidates than the definition itself. Inversion of control is the principle: instead of your code calling a framework, the framework calls your code. Instead of a module pulling its dependencies, the dependencies are pushed in. DI is one specific technique for achieving IoC — but event callbacks, template method patterns, and plugin architectures are also forms of IoC.

Saying "dependency injection and inversion of control are the same thing" in an interview is a red flag. The correct framing is: DI is a way to practice IoC. IoC is the broader design principle.

The service locator trap interviewers listen for

The service locator pattern is the other easy confusion. A service locator is a registry: your module calls `locator.get<ILogger>()` and gets back an implementation. It looks like DI because the module doesn't create its own logger. But it's not DI — the dependency is still hidden. You can't see from the outside what `SomeModule` needs, because it asks for things at runtime rather than declaring them at construction time.

Mark Seemann's writing at blog.ploeh.dk makes this distinction clearly: the service locator is considered an anti-pattern in most modern architecture guidance precisely because it makes dependencies invisible and testing harder, not easier.

What this looks like in practice

Compare these two:

The first version is harder to test, harder to read, and introduces a global dependency on the locator itself. The second version is honest about what it needs. In an interview, if you're asked to choose between them, choose the second and explain why.

Talk about lifetime and ownership like someone who has shipped code

Transient, scoped, and singleton are not just container jargon

These three lifetime labels come from DI containers in managed languages, but the underlying concepts apply everywhere — including plain C and C++. Transient means a new instance is created every time it's needed. Scoped means the instance lives for the duration of a logical operation (a request, a transaction, a task). Singleton means one instance exists for the life of the process.

In non-managed code, you don't get a container to enforce these lifetimes. You enforce them yourself through the structure of your composition root — where you create objects, how long they stay in scope, and what you pass where.

What this looks like in practice

The fix is straightforward: make the lifetime explicit. Either extend the logger's scope to match the engine's, use a shared pointer with explicit ownership semantics, or pass ownership into the engine at construction. The point is that the service locator pattern hides this problem — when dependencies are injected explicitly, lifetime mismatches become visible at the wiring site.

The failure mode interviewers are checking for

The mistake isn't forgetting the word "transient." It's not understanding that in C++, a reference or raw pointer to an injected dependency is a promise that the dependency will outlive the object holding it. Interviewers who ask about lifetime are checking whether you've actually debugged a dangling reference or a use-after-free in a DI-wired system. The answer that signals experience is one that names the failure mode first, then explains the safe wiring.

Know when DI helps and when it's just ceremony

Why DI is useful without pretending it's magic

The honest case for DI: it makes dependencies explicit, it makes testing straightforward, and it makes swapping implementations — real vs. fake, production vs. staging, hardware vs. simulator — a wiring decision rather than a code change. Those benefits are real and they compound in larger codebases where multiple teams touch the same interfaces.

Constructor injection specifically forces a useful discipline: you cannot build an object without providing everything it needs. That constraint eliminates a class of bugs where objects are used before they're fully initialized.

What this looks like in practice

A small C project with three files and one hardware target probably doesn't need DI at all. If `sensor.c` always calls the same `hal_read()` and will never be tested in isolation, adding a struct of function pointers is machinery without payoff. The right call is direct wiring: call the function, don't abstract it, move on.

The team's decision to skip DI on a single-board project with no unit test requirement isn't laziness — it's proportionality. Abstraction has a cost: more indirection, more places to look when something breaks, more cognitive load for the next person reading the code.

The line interviewers want you to see

Use DI when the module needs to be tested in isolation, when implementations will vary across environments, or when the codebase is large enough that hidden dependencies create real maintenance cost. Skip it when the module is small, static, and the dependency will never change. An interviewer asking this question wants to know whether you reach for DI reflexively or deliberately. Deliberate is the right answer.

Answer follow-up questions without sounding like you memorized a glossary

If they ask for an example, start with the wiring

When an interviewer asks for a concrete example, the instinct is to describe the pattern abstractly again. Resist it. Start at the wiring: "I create a `FakeLogger` that implements the interface, pass it into the constructor, and the module under test never knows it's not the real thing." That sentence shows you understand the mechanism, not just the vocabulary.

The composition root — where the real object graph is assembled — is worth mentioning explicitly if the conversation goes there. It's the place where all the wiring decisions live, and it's the place that changes when you swap implementations. Everything else stays the same.

If they ask about containers, keep it grounded

Basic framework registration in a DI container is just centralized object creation with lifecycle management. The container knows how to build each type and wires them together when asked. In C++ you might use a lightweight container or write your own factory. In plain C you write the wiring by hand in `main` or an init function.

The interview should focus on the dependency boundary, not the container brand. What matters is that the module declares what it needs, and the composition root provides it. Whether that's done by Boost.DI, a hand-rolled factory, or ten lines of `main` is an implementation detail.

What this looks like in practice

A common follow-up is: "How would you test this?" The answer is the same in C and C++: swap the real dependency for a fake at the injection point.

That test doesn't touch hardware, doesn't depend on a real clock, and runs in milliseconds. The injected dependency is the seam. The CppUTest framework documentation covers exactly this pattern for C and C++ unit testing in embedded contexts.

When an interviewer hears that answer — "I swap in a fake at the injection point and assert on the fake's recorded calls" — they hear someone who has actually written a test this way, not someone who read the Wikipedia article.

FAQ

Q: How do you explain dependency injection clearly in 30 seconds during a C or C++ interview?

Say this: "A module receives its dependencies from the outside instead of creating them itself. In C++, that means constructor arguments. In plain C, that means a struct of function pointers or a context pointer passed at init time. The payoff is testability — you swap the real dependency for a fake without changing the module." That runs under 25 seconds and covers definition, mechanism, and benefit.

Q: What is the difference between dependency injection, inversion of control, and the service locator pattern?

Inversion of control is the broad principle: control over dependency lookup is inverted away from the module. DI is one technique for achieving IoC — dependencies are pushed in rather than pulled. The service locator is a different technique that looks similar but hides dependencies behind a registry lookup, making them invisible from the outside and harder to replace in tests.

Q: How would you implement dependency injection in plain C without a framework?

Define a struct of function pointers that represents the module's dependencies. Pass that struct into the module's init function. The module stores it and calls through it. In tests, fill the struct with fakes. In production, fill it with real implementations. No framework needed — just explicit wiring at startup.

Q: How would you use dependency injection in C++ using constructors or interfaces?

Define an abstract base class (the interface) with pure virtual methods. Have the real implementation and the test double both inherit from it. Pass a reference or pointer to the interface into the dependent class's constructor. The class stores the reference and calls through it, never knowing which concrete type it received.

Q: What changes when dependency injection is used in embedded systems with tight memory and timing constraints?

Dynamic allocation is usually off the table, so dependencies are statically allocated and passed by pointer or struct copy at init time. There's no runtime container. Lifetime management is explicit and deterministic. The struct-of-function-pointers pattern fits these constraints well because it has no heap footprint and zero runtime overhead beyond a function pointer call.

Q: What are the main lifetime and ownership pitfalls interviewers expect you to know?

The primary one is holding a reference or raw pointer to a dependency that dies before the object using it — a dangling reference. A second is unclear ownership: if both the injected object and the container think they own the dependency, you get a double-free. The safe rule is that whoever creates the dependency owns it, and the injected object holds a non-owning reference or pointer with a clearly understood lifetime contract.

Q: When is dependency injection unnecessary or harmful in a small C project?

When the module is small, has one implementation, will never be tested in isolation, and the dependency will never change. Adding a struct of function pointers in that context introduces indirection without payoff. The right move is direct wiring. DI is a tool for managing variability and testability — if neither applies, the tool doesn't earn its cost.

Q: How do you prove that DI improves testability with a realistic example?

Take a module that calls `time(NULL)` directly. Write a test for it — you can't control what time it returns, so the test is either fragile or untestable. Now inject a `get_time` function pointer. In the test, pass a fake that returns a fixed timestamp. The module's behavior is now fully deterministic under test. That's the proof: injection is what makes the seam available.

How Verve AI Can Help You Prepare for Your Software Engineer Job Interview

The structural problem this article addresses — knowing the concept but freezing when you have to say it out loud — doesn't go away from reading. It goes away from practice. Specifically, from saying the 30-second answer out loud to something that responds to what you actually said, not a canned prompt.

Verve AI Interview Copilot is built for exactly that gap. It listens in real-time to your spoken answer and responds to the specific words you used — so if you said "the module creates its own dependencies" when you meant "receives them," the follow-up catches that. Verve AI Interview Copilot doesn't just run you through a question list; it tracks the answer you gave and surfaces the follow-up an interviewer would actually ask next. For a topic like dependency injection, where the dangerous zone is the second question ("okay, but who owns the injected object?"), that responsiveness is what makes the practice transfer to the real room. Verve AI Interview Copilot stays invisible while it works, so you can practice in conditions that feel like the actual interview. The capability that changes the calculus here is that it responds to what you said — not what you were supposed to say.

Conclusion

You now have the short answer, the longer answer, a C++ example you can sketch in two minutes, and a plain-C version that holds up in an embedded conversation. You know the difference between DI and IoC, you know why the service locator is the wrong answer even when it looks convenient, and you know what interviewers are actually checking when they ask about lifetime.

The one thing left is to say the 30-second version out loud. Not in your head — out loud, at normal speaking pace, without reading it. That's the part that stops the freeze. The sentence exists. Now make it yours.

JM

James Miller

Career Coach

Ace your live interviews with AI support!

Get Started For Free

Available on Mac, Windows and iPhone