Linked List design and implementation - C++
You can simplify your code a lot.

    if(head == NULL)
    {
        n-&gt;prev = NULL;
        n-&gt;next = NULL;
        head = n;
    }
    else
    {
        n-&gt;next = head;
        n-&gt;prev = NULL;
        head = n;
    }


Both sides of this else look identical.
I think you can shorten your code with:

void list:: add_to_head(int num)
{
    nodePtr oldHead = head;
    head = new node{num, oldHead, nullptr);
    // Also you forget to do this bit.
    if (oldHead) {
        oldHead-&gt;prev = head;
    }
}


@pm100 covered the temporary variable.

nodePtr temp;
nodePtr prev;


You don't need these as a class member. It should be local to the member functions where they are used.

If I was you I would add another member though.

nodePtr tail;


Then you don't need to search for the tail every time you add a new element.

Const correctness. In a lot of code we pass variables by reference and const reference. If you pass an object by const reference you can only call const members on it.

So you should take care to mark functions that don't mutate the object as const. This will allow you to call the methods from a const reference. Also it enables some compiler optimizations when it knows you are not mutating the object.

So the following functions should probably be const.

void check_duplicates(int) const;
void print() const;


Your print() is fine. But I would make it more generic. Rather than print to std::cout you should print to a generic stream (you can always default to std:::cout.

print(std::ostream&amp; str = std::cout) const;


Also in C++ printing is usually done via operator&lt;&lt;. So I would add this function. All it needs to do is call the print function passing the stream.

std::ostream&amp; operator&lt;&lt;(std::ostream&amp; str, List const&amp; data)
{
     data.print(str);
     return str;
}