Howdy, this blog post should explain what a linked list is and provide a Github link to my implementation of a linked list.
What is a linked list? It basically is a list of nodes, each node contains data(In my case a int32_t) and a pointer to the next node. Thanks to Wikipedia here is a visualization of what I mean 
I also want to mention that the data which a node contains can be anything you wish. In my example I used a int because it is the quickest, additionally if you use for example a object as data, then you have to add the
bool operator==(const Object& other) const
{
    return (importantVariable == other.importantVariable) && (moreImportantVariable == other.moreImportantVariable);
}
As already mentioned a linked list works with pointers to the next object (singly linked list), this means a linked list is not layed out in a continues block of memory but it is scattered across the memory unlike arrays which indeed live in a continues block of memory. This also means that the size of a linked list is variable and can be changed during runtime unlike a array.
The linked list starts with the head and grows in direction of the end. The head is always our starting point.
void insertNodeAtEnd( int32_t value )
{
      Node* currNode = this;
      // We iterate over the list to find the last node
      while ( currNode->next != nullptr )
      {
          currNode = currNode->next;
      }
      // At this point we reached the end
      Node* newNode  = new Node( value );
      currNode->next = newNode;
}
Node* insertNodeAtHead( int32_t value )
{
     // We know that we have to add the new node at the beginning
     // We just need to adjust the next element of the new node to point to
     // the last head
     Node* newNode = new Node( value );
     newNode->next = this;
     return newNode;
}
    Node* deleteNode( int32_t value )
    {
        // If the head node contains the value we want to delete
        if ( data == value )
        {
            Node* newHead = next;
            delete this;
            return newHead;
        }
        Node* currNode = this;
        while ( currNode->next != nullptr )
        {
            if ( currNode->next->data == value )
            {
                // We save the address of the node which is to be deleted in tmp
                Node* tmp = currNode->next;
                // We adjust the next of the current node pointing to the node
                // after the to be deleted one Basically we just skip the node
                // in between
                currNode->next = currNode->next->next;
                delete tmp;
                return this;
            }
            currNode = currNode->next;
        }
        return this;
    }
This are the most important functions. The other functions I included in my code basically all work the same. Iterating and checking if next != nullptr.
I will add implementation and description of it later maybe.
// The 0th element would be the end
Node* getNthNodeFromEnd( int32_t n )
{
  int32_t listLen = getSize();
  if ( n > listLen )
  {
      std::cout << "N larger than size of linked list\n";
      return nullptr;
  }
  int32_t nthElement = listLen - n;
  Node*   currNode   = this;
  // Starting at one because we are at the head alrady
  for ( int32_t i = 1; i < nthElement; ++i )
  {
      currNode = currNode->next;
  }
  return currNode;
}
Node* getNthNodeFromEndDoublePointer( int32_t n )
{
 if( n > getSize() )
 {
     std::cout << "N larger than size of linked list" << std::endl;
     return nullptr;
 }
 Node* lazy = this;
 Node* runner = lazy;
 for( int32_t i = 0; i < n; ++i )
 {
     runner = runner->next;
 }
 while( runner->next != nullptr )
 {
     runner = runner->next;
     lazy = lazy->next;
 }
 return lazy;
}