Monday, October 5, 2026

 Here’s a C++ template interview cheat sheet with each question followed by an interview-ready answer, explanation, and example.


1. What is a template in C++? Why do we use templates?

Answer:

 A template allows us to write generic code that works with different data types without duplicating the implementation.

There are two main types:

* Function templates

* Class templates

Example

cpp

template <typename T>

T add(T a, T b)

{

    return a + b;

}


Now the same function can work with multiple types:

cpp

add(10, 20);          // int

add(2.5, 3.5);        // double

add(std::string("A"), "B");  // string


The compiler generates the appropriate version when the template is instantiated.

Why use templates?

* Code reuse

* Type safety

* Compile-time polymorphism

* Generic programming

* Often zero runtime overhead

Interview point: Templates are primarily a mechanism for compile-time polymorphism, whereas virtual functions provide runtime polymorphism.


───


2. What's the difference between typename and class in templates?

For declaring a template type parameter, they are generally equivalent:

cpp

template <typename T>

class Box {};


and:

cpp

template <class T>

class Box {};


Both mean that T is a type.

But typename has another important use

Consider:

cpp

template <typename T>

void foo()

{

    T::value_type x;

}


This is problematic because the compiler doesn't initially know whether T::value_type is a type or something else.

We tell the compiler explicitly:

cpp

template <typename T>

void foo()

{

    typename T::value_type x;

}


Here, typename means:

T::value_type is a type.

Interview trap

Don't say "typename and class are always interchangeable."

They're interchangeable when declaring a template type parameter, but typename has additional meaning for dependent names.


───


3. What is template specialization?

Specialization allows us to provide a different implementation for a particular type or category of types.

There are two important forms.

Full specialization

cpp

template <typename T>

class Printer

{

public:

    void print()

    {

        std::cout << "Generic\n";

    }

};


For int, we can provide a completely different implementation:

cpp

template <>

class Printer<int>

{

public:

    void print()

    {

        std::cout << "Integer\n";

    }

};


Now:

cpp

Printer<double> p1;

p1.print();       // Generic


Printer<int> p2;

p2.print();       // Integer


Partial specialization

We can specialize a class template for a category of types.

cpp

template <typename T>

class Printer

{

};


For pointers:

cpp

template <typename T>

class Printer<T*>

{

};


Now:

cpp

Printer<int> p1;     // primary template

Printer<int*> p2;    // pointer specialization


Important interview question

Can function templates be partially specialized?

No.

Class templates can be partially specialized.

Function templates can instead be overloaded.


───


4. What's the difference between function template specialization and overloading?

Consider:

cpp

template <typename T>

void print(T value)

{

    std::cout << "Template\n";

}


We can specialize it:

cpp

template <>

void print<int>(int value)

{

    std::cout << "Specialization\n";

}


Or we can overload it:

cpp

void print(int value)

{

    std::cout << "Overload\n";

}


These are not equivalent.

Why?

Overloading participates in overload resolution.

Template specialization happens after the appropriate template has been selected.

This can lead to surprising behavior, particularly when templates are called through other templates.

Interview recommendation

If asked:

"Should I specialize a function template or overload it?"

A good answer is:

Function templates generally cannot be partially specialized, and overloading is often preferable when customizing function behavior. Class template specialization is the normal mechanism for specialization.


───


5. What is SFINAE?

SFINAE stands for:

Substitution Failure Is Not An Error

It means that when substituting template arguments causes an invalid type or expression in the immediate context, that template can simply be removed from consideration rather than producing a compilation error.

A classic example:

cpp

template <typename T>

typename T::value_type getValue(T obj)

{

    return obj[0];

}


Suppose:

cpp

std::vector<int> v;

getValue(v);


std::vector<int> has:

cpp

value_type


so the template works.

But:

cpp

int x;

getValue(x);


doesn't have int::value_type.

With SFINAE, we can use this fact to control which functions participate in overload resolution.

Common SFINAE tools

cpp

std::enable_if

std::void_t

std::is_same

std::is_integral

std::is_convertible


For example:

cpp

template <

    typename T,

    typename = std::enable_if_t<std::is_integral_v<T>>

>

void foo(T value)

{

    std::cout << "Integral\n";

}


This function only participates when T is an integral type.

Modern C++

C++20 concepts provide a much cleaner solution:

cpp

template <std::integral T>

void foo(T value)

{

}


So an excellent interview answer is:

SFINAE was historically used to constrain templates. In modern C++, concepts are usually preferred because they're clearer and produce better diagnostics.


───


6. What are variadic templates?

A variadic template can accept zero or more template arguments.

cpp

template <typename... Args>

void print(Args... args)

{

}


Args... is called a parameter pack.

For example:

cpp

print(1, 2.5, "hello", 'A');


Args could represent:

text

int

double

const char*

char


Fold expressions

C++17 introduced fold expressions, making variadic templates much easier to use.

For example:

cpp

template <typename... Args>

auto sum(Args... args)

{

    return (args + ...);

}


Then:

cpp

auto result = sum(1, 2, 3, 4);


Conceptually:

cpp

((1 + 2) + 3) + 4


Why are variadic templates useful?

They're heavily used in:

* std::tuple

* std::make_unique

* std::make_shared

* std::format

* Generic wrappers

* Perfect forwarding


───


7. What is template instantiation?

A template is essentially a blueprint. Instantiation happens when the compiler needs a concrete version of that template.

Example:

cpp

template <typename T>

T square(T x)

{

    return x * x;

}


When we write:

cpp

square(5);


the compiler can instantiate:

cpp

int square(int x)

{

    return x * x;

}


And:

cpp

square(2.5);


can result in:

cpp

double square(double x)

{

    return x * x;

}


Three concepts worth knowing

Implicit instantiation

cpp

square(5);


The compiler generates what's needed automatically.

Explicit specialization

cpp

template <>

int square<int>(int x)

{

    // special implementation

}


Explicit instantiation

cpp

template int square<int>(int);


This explicitly tells the compiler to instantiate the template for int.

Interview follow-up

Why are templates usually defined in header files?

Because the compiler generally needs to see the template definition at the point of instantiation.

This is why you'll commonly see:

cpp

// MyClass.h

template <typename T>

class MyClass

{

    ...

};


rather than putting the implementation only in a .cpp file.


───


8. What are dependent names, and why do we need typename?

This is one of the classic advanced template questions.

Consider:

cpp

template <typename T>

void foo()

{

    T::value_type x;

}


The problem is that T is a dependent type. The compiler doesn't know what T will be yet.

Therefore, it doesn't know whether:

cpp

T::value_type


is a type or something else.

We tell the compiler:

cpp

template <typename T>

void foo()

{

    typename T::value_type x;

}


The typename says:

Treat T::value_type as a type.

Another related keyword: template

You can encounter:

cpp

obj.template foo<int>();


The template keyword tells the compiler that foo should be interpreted as a template when obj depends on a template parameter.

These two keywords are frequently tested together in senior C++ interviews.


───


9. What are C++20 concepts?

Concepts allow us to specify requirements on template parameters.

Before C++20, you might write:

cpp

template <

    typename T,

    typename = std::enable_if_t<std::is_arithmetic_v<T>>

>

T add(T a, T b)

{

    return a + b;

}


This works, but it's difficult to read.

With C++20:

cpp

template <typename T>

concept Numeric = std::is_arithmetic_v<T>;


template <Numeric T>

T add(T a, T b)

{

    return a + b;

}


Or using a standard concept:

cpp

template <std::integral T>

T add(T a, T b)

{

    return a + b;

}


Advantages of concepts

* More readable

* Better compiler errors

* Clearly communicates requirements

* Cleaner overload resolution

* Replaces many traditional SFINAE techniques

Interview answer

A good concise answer:

Concepts are named compile-time constraints on template parameters introduced in C++20. They make template requirements explicit and generally provide clearer diagnostics than SFINAE.


───


10. What is perfect forwarding, and how is it related to templates?

This is probably the most important advanced topic on this list.

Consider:

cpp

template <typename T>

void wrapper(T&& arg)

{

    foo(std::forward<T>(arg));

}


Here T&& can be a forwarding reference when T is deduced.

It allows the wrapper to preserve whether the caller passed an lvalue or rvalue.

Example

cpp

void foo(const std::string& s)

{

    std::cout << "lvalue\n";

}


void foo(std::string&& s)

{

    std::cout << "rvalue\n";

}


Now:

cpp

std::string s = "hello";


wrapper(s);                  // lvalue

wrapper(std::string("hi"));  // rvalue


std::forward<T> preserves that value category.

Why not just use std::move?

This is an important interview question.

std::moveunconditionally casts its argument to an rvalue.

std::forward<T> conditionally casts based on the original type/value category.

So:

cpp

std::move(x)


means roughly:

"Treat x as an rvalue."

Whereas:

cpp

std::forward<T>(x)


means:

"Preserve whether the caller originally gave me an lvalue or rvalue."

Reference collapsing

Perfect forwarding relies on reference collapsing rules:


Combination

Result


T& &

T&


T& &&

T&


T&& &

T&


T&& &&

T&&



This is very commonly asked in senior C++ interviews.


───


🔥 The 10 to memorize

If you want a compact interview revision list:


#

Question

Key concept


1

What are templates?

Generic/compile-time programming


2

typename vs class?

Template parameters + dependent types


3

What is specialization?

Full vs partial specialization


4

Specialization vs overloading?

Overload resolution


5

What is SFINAE?

Substitution failure


6

What are variadic templates?

Parameter packs/fold expressions


7

What is template instantiation?

Implicit/explicit instantiation


8

What are dependent names?

typename, template


9

What are concepts?

C++20 constraints


10

What is perfect forwarding?

Forwarding references/reference collapsing



For a senior C++ interview, I'd spend the most time on #5, #8, #9 and #10. Those are where interviewers can quickly move from basic template knowledge into deeper C++ understanding.

 Here’s a C++ lambda syntax cheat sheet from basics → advanced, with the pieces building up progressively.

1. Basic lambda
cpp
[]() {
    std::cout << "Hello";
};
General syntax:
cpp
[capture](parameters) -> return_type {
    // body
};
The -> return_type is usually optional because C++ can deduce it.
cpp
[]() {
    return 10;
};

───
2. Store a lambda in a variable
cpp
auto add = [](int a, int b) {
    return a + b;
};
std::cout << add(2, 3);   // 5
Think of a lambda as an unnamed function object.

───
3. Parameters
cpp
auto multiply = [](int a, int b) {
    return a * b;
};
multiply(3, 4);
Explicit return type:
cpp
auto divide = [](int a, int b) -> double {
    return static_cast<double>(a) / b;
};

───
4. Capture list []
The capture list controls which outside variables the lambda can access.
cpp
int x = 10;
auto f = []() {
    // std::cout << x;  // ❌ x not captured
};
Capture x by value:
cpp
int x = 10;
auto f = [x]() {
    std::cout << x;
};
Capture x by reference:
cpp
int x = 10;
auto f = [&x]() {
    x = 20;
};

───
5. Capture everything
Capture everything by value
cpp
int x = 10;
int y = 20;
auto f = [=]() {
    std::cout << x << y;
};
Capture everything by reference
cpp
int x = 10;
int y = 20;
auto f = [&]() {
    x++;
    y++;
};
Mix value and reference
cpp
int x = 10;
int y = 20;
auto f = [x, &y]() {
    // x → copied
    // y → referenced
};
You can also write:
cpp
[x, &y]

───
6. Mutable lambda
This is an important concept.
By default, variables captured by value cannot be modified inside the lambda:
cpp
int x = 10;
auto f = [x]() {
    // x++;  // ❌
};
Use mutable:
cpp
int x = 10;
auto f = [x]() mutable {
    x++;
    std::cout << x;
};
f();  // 11
f();  // 12
std::cout << x;  // 10
Notice:
text
Original x       = 10
Lambda's copy    = 10 → 11 → 12
mutable changes the lambda's captured copy, not the original variable.

───
7. Lambda with no parameters
cpp
auto hello = [] {
    std::cout << "Hello";
};
The () can be omitted when there are no parameters.
Equivalent:
cpp
[]() {
    std::cout << "Hello";
};

───
8. Lambda with return type
Usually:
cpp
auto f = [](int x) {
    return x * 2;
};
Compiler deduces int.
Explicit:
cpp
auto f = [](int x) -> int {
    return x * 2;
};
Useful when deduction is problematic or when you want to be explicit.

───
9. Lambda passed to an algorithm
This is where lambdas become extremely useful.
cpp
std::vector<int> v = {1, 2, 3, 4, 5};
std::for_each(v.begin(), v.end(), [](int x) {
    std::cout << x << " ";
});
Sorting:
cpp
std::sort(v.begin(), v.end(), [](int a, int b) {
    return a > b;
});
Result:
text
5 4 3 2 1

───
10. Lambda with std::find_if
cpp
auto it = std::find_if(
    v.begin(),
    v.end(),
    [](int x) {
        return x > 10;
    }
);
The lambda acts as a predicate.

───
11. Generic lambda — C++14
Instead of specifying parameter types:
cpp
auto print = [](auto x) {
    std::cout << x;
};
print(10);
print(3.14);
print("Hello");
The lambda effectively behaves like a function template.
Conceptually:
text
print(int)
print(double)
print(const char*)

───
12. Generic lambda with multiple parameters
cpp
auto add = [](auto a, auto b) {
    return a + b;
};
add(10, 20);
add(2.5, 3.5);

───
13. Generic lambda with forwarding references
More advanced:
cpp
auto f = [](auto&& x) {
    // x can bind to lvalue or rvalue
};
This is commonly used when writing generic code.
For perfect forwarding:
cpp
auto f = [](auto&& x) {
    some_function(std::forward<decltype(x)>(x));
};

───
14. Lambda returning a lambda
Yes, lambdas can return lambdas.
cpp
auto createAdder = [](int x) {
    return [x](int y) {
        return x + y;
    };
};
auto add10 = createAdder(10);
std::cout << add10(5);  // 15
Here:
text
createAdder(10)
       ↓
lambda capturing x = 10
       ↓
add10(5)
       ↓
15

───
15. Immediately Invoked Lambda — IIFE
You can create and immediately execute a lambda:
cpp
int result = [](int a, int b) {
    return a + b;
}(10, 20);
result becomes 30.
This:
cpp
[](int a, int b) {
    return a + b;
}(10, 20);
means:
Create lambda → immediately call it with 10, 20.


───
16. Capture this
Inside a member function:
cpp
class A {
    int value = 10;
public:
    void foo() {
        auto f = [this]() {
            std::cout << value;
        };
        f();
    }
};
[this] captures the this pointer.
You can therefore access:
cpp
value
which is essentially:
cpp
this->value

───
17. Capture *this — C++17
This is different.
cpp
auto f = [*this]() {
    std::cout << value;
};
[*this] captures a copy of the object.
Compare:
cpp
[this]
with:
cpp
[*this]

Capture
Captures
[this]
this pointer
[*this]
copy of the object

This distinction becomes important when the lambda outlives the object.


───
18. Init capture — C++14
You can create a new variable inside the capture list:
cpp
int x = 10;
auto f = [y = x + 5]() {
    std::cout << y;
};
y is a new variable belonging to the lambda.
Very useful for moving objects:
cpp
auto ptr = std::make_unique<int>(10);
auto f = [p = std::move(ptr)]() {
    std::cout << *p;
};
Now ownership of the unique_ptr has been moved into the lambda.


───
19. Generalized lambda capture
You can have multiple init captures:
cpp
int x = 10;
auto f = [
    a = x + 1,
    b = x * 2
]() {
    std::cout << a << b;
};

───
20. constexpr lambda
Modern C++ allows constexpr lambdas:
cpp
constexpr auto square = [](int x) {
    return x * x;
};
constexpr int result = square(5);
result can be evaluated at compile time.
───


21. Lambda with constraints — C++20
Generic lambda:
cpp
auto add = [](auto a, auto b) {
    return a + b;
};
You can constrain it:
cpp
auto add = []<typename T>(T a, T b) {
    return a + b;
};
With concepts:
cpp
auto add = []<std::integral T>(T a, T b) {
    return a + b;
};
Now the lambda only accepts integral types.
This <typename T> syntax is called a template parameter list for the lambda and was introduced in C++20.


───
22. Lambda conversion to function pointer
A lambda with no captures can convert to a function pointer:
cpp
auto f = [](int x) {
    return x * 2;
};
int (*ptr)(int) = f;
std::cout << ptr(5);
But this doesn't work for a capturing lambda:
cpp
int x = 10;
auto f = [x](int y) {
    return x + y;
};
// int (*ptr)(int) = f;  // ❌
Why?
Because the lambda needs stored state (x).


───
23. std::function
You can store lambdas in std::function:
cpp
std::function<int(int, int)> add =
    [](int a, int b) {
        return a + b;
    };
std::cout << add(2, 3);
This is useful when you need a common callable type.
But std::function has some overhead, so don't automatically use it everywhere.


───
24. Lambda as a comparator
Very common in interviews:
cpp
std::sort(v.begin(), v.end(),
    [](int a, int b) {
        return a < b;
    });
For objects:
cpp
std::sort(students.begin(), students.end(),
    [](const Student& a, const Student& b) {
        return a.age < b.age;
    });


───
25. Recursive lambda
A lambda cannot simply refer to itself by its own variable during its initialization:
cpp
// ❌
auto factorial = [](int n) {
    return n * factorial(n - 1);
};
One common solution is std::function:
cpp
std::function<int(int)> factorial =
    [&](int n) {
        if (n <= 1)
            return 1;
        return n * factorial(n - 1);
    };
Modern C++ also allows more efficient patterns using an explicit self parameter:
cpp
auto factorial = [](this auto&& self, int n) {
    if (n <= 1)
        return 1;
    return n * self(n - 1);
};
The explicit object parameter form above is C++23.


───
26. The full syntax to remember
The most useful mental model is:
cpp
[captures] <template_params> (parameters)
    mutable
    constexpr
    noexcept
    -> return_type
{
    body
}
Not every part is required.
For example:
cpp
auto f =
    [x, &y]                 // capture
    <typename T>            // template parameters (C++20)
    (T value)               // parameters
    mutable                 // mutable
    noexcept                // noexcept
    -> T                    // return type
{
    // body
};

───
The progression I'd memorize
text
[]() { }
 ↓
[](int x) { }
 ↓
[x](int y) { }
 ↓
[&x](int y) { }
 ↓
[=](int y) { }
 ↓
[&](int y) { }
 ↓
[x]() mutable { }
 ↓
[x = std::move(obj)]() { }
 ↓
[](auto x) { }                    // C++14
 ↓
[*this]() { }                     // C++17
 ↓
[]<typename T>(T x) { }           // C++20
 ↓
[](this auto&& self) { }          // C++23
If you're preparing for a C++ interview, the most important lambda topics are: capture by value/reference, mutable, this vs *this, init-capture, generic lambdas, lambda-to-function-pointer conversion, std::function, and using lambdas with STL algorithms.