Doubly-linked lists are different from single-linked lists because travel is both ways. The key differentiating factors in a doubly-linked list are the first and last elements because they perform a critical role in this data structure’s function. Therefore, it is common to hear of data being carried from two connecting fields, which are previous and the next.

Also, note that the reference to next in the last element in a double-linked list points to null, which indicates the end of the list. Meanwhile, the previous node’s reference in the first node points to null.

So when looking at a double-linked list, look out for the following three key items:

1. The value of the given node.
2. The link to the previous node.
3. The link to the next node.

### How to create a Double-Linked List

The process here is almost the same as that of a singly linked list. Except that here, both the next and previous pointers are essential in creating links for every node. The node has data.

First, lets create a generic node class as follows:

```class Node:
def __init__(self, data):
self.data =data
self.next =None
self.prev =None

# creating a new element in a doubly linked list
def createNewElement(self, NewVal):
NewNode = Node(NewVal)

### How to insert a new Node in a Doubly-Linked List

This involves putting a new element between two already existing elements in a linked list. In this case, you have to determine where you want to insert your new node.

```#code to insert element (new_val) aftera specified node (prev_node)
def insertElement(self, prev_node, new_val):
if prev_node is None:
return
NewNode =Node(new_val)
NewNode.next =prev_node.next
prev_node.next = NewNode
if NewNode.next is not None:
NewNode.next.prev =NewNode```

### How to insert a given element before a specific Node in a Doubly-Linked List

This method can be called when we want to insert our new element before a specified node in a given doubly-linked list.

Lets say we have a linked list with the following elements,

62, 8, 12

When we insert 99 before 8 the new linked list will have the following elements.

62, 99, 8, 12

The complete code for the method is as follows:

```# insert an element before a given node
# check if the specified next_node is NULL
if (next_node == None):
print("the specified next node cannot be NULL")
return

# place the data in the new node
new_node = Node(new_data)

# make previous of the new node as previous of next node
new_node.prev = next_node.prev

# make the previous of the next node as the new node
next_node.prev = new_node

# make the next node as next of new node
new_node.next = next_node

# alter next of the new node's previous node
if (new_node.prev != None):
new_node.prev.next = new_node

# make it the new head node if the previous of new node is NULL
else:

### Appending an element in a Doubly-Linked List

This involves adding a new element at the end of the linked lists. It involves keeping track of the last element in the doubly linked list as it is.

```#code to append an item to a doubly linked list -adds element at the end
def appendElement (self, NewVal):
NewNode =Node(NewVal)
NewNode.next =None
NewNode.prev =None
return
while (last.next is not None):
last =last.next
last.next =NewNode
NewNode.prev =last
return```

### How to get the size of a Doubly-Linked List

This section intends to find the size of the given doubly linked list. To do this, we need to traverse a doubly linked list from the head node to the tail node or what is sometimes called the last node.

```# function to calculate the size of a doubly linked list
_size = 0
while (node != None):
_size = _size + 1
node = node.next

return _size```

Traversing a linked list means visiting every node in the entire linked list. Traversing is very common with singly-linked lists and doubly-linked lists. It encompasses moving from the node at the head to the tail’s node.

After visiting the node, some operation is usually carried out, for instance, comparing the data in the node with some search term, inserting a new element before or after the specified node, or even deleting the node entirely.

In this example, we will print the current node. Subsequently, we will check if the current node is the last. If it is not, then we move to the next node till we reach the last node in the linked list.

```# traverse a doubly linked list
# check if there is a node first before performation the operations
while (node is not None):
print(node.data)
node = node.next```

### Deleting a node at the beginning of a Doubly-Linked List

It is easy to delete an item in a doubly-linked list from the front. An important check before attempting any delete tricks is to verify any items in the list.

If the number of items in the list is only one, then all you have to do is set the first node to point to None.

However, if the number of items in the list exceeds one, then set the value of the previous reference the node at the starting point to None. But start by making sure to set the value of the starting node to the value of the next node.

```# delete head node in a doubly linked list
def deleteElementFromStart(self):
print("The list is empty")
return

### How to delete a tail node in a Doubly-Linked List

Just like in deleting a head node, the first check is to ascertain if the list has more than one item, one item, or the list is empty. If the list is empty, we return. But if there is one item in the linked list, all we have to do is set the node to point to None.

In case there is more than one item, we traverse the linked list from the beginning to the end. Subsequently, we identify the previous node relative to the last node then sets its reference to the next node as None.

That’s all we have to do to remove the last item from the list.

```# delete tail node in a doubly linked list
def deleteElementFromEnd(self, node):
if node is None:
print("The list is empty")
return
if node.next is None:
return
while new_next.next is not None:
new_next = new_next.next
new_next.prev.next = None```

### Delete any node from a Doubly-Linked List

Deleting a specific node involves passing the given node to the function. The function will then do several checks before deleting the given element. First, it will check if the linked lists have a head. If there is no head, it returns. Next is to check if there is a node. If the node passed to the function is None, the program returns. The other checks in the sequence include if the head is equivalent to the given node if there is a next node or a previous node relative to the given node. Below is the full implementation of the method.

```# delete any node from a doubly linked list
def deleteAnyElement(self,node):
print("The list is empty")
return
if node is None:
print("There is no node to delete")
return

# if the node to deleted is NOT the last node, then  only change next
if node.next is not None:
node.next.prev = node.prev

# if node to be deleted is NOT the head node, then only change the prev
if node.prev is not None:
node.prev.next = node.next```

### Complete Implementation of a Doubly-Linked List

```# creating a new element in a doubly linked list
def createNewElement(self, NewVal):
NewNode = Node(NewVal)

#code to insert element (new_val) between nodes
def insertElement(self, prev_node, new_val):
if prev_node is None:
return
NewNode =Node(new_val)
NewNode.next =prev_node.next
prev_node.next = NewNode
if NewNode.next is not None:
NewNode.next.prev =NewNode

#code to append to a doubly linked list -adds element at the end
def appendElement (self, NewVal):
NewNode =Node(NewVal)
NewNode.next =None
NewNode.prev =None
return
while (last.next is not None):
last =last.next
last.next =NewNode
NewNode.prev =last
return

# function to calculate the size of a doubly linked list
_size = 0
while (node != None):
_size = _size + 1
node = node.next
return _size

# insert an element before a given node
# check if the specified next_node is NULL
if (next_node == None):
print("the specified next node cannot be NULL")
return

# place the data in the new node
new_node = Node(new_data)

# make previous of the new node as previous of next node
new_node.prev = next_node.prev

# make the previous of the next node as the new node
next_node.prev = new_node

# make the next node as next of new node
new_node.next = next_node

# alter next of the new node's previous node
if (new_node.prev != None):
new_node.prev.next = new_node

# make it the new head node if the previous of new node is NULL
else:

# traverse a doubly linked list
# check if there is a node first before performation the operations
while (node is not None):
print(node.data)
# last = node
node = node.next

def deleteElementFromStart(self):
print("The list is empty")
return

# delete tail node in a doubly linked list
def deleteElementFromEnd(self, node):
if node is None:
print("The list is empty")
return
if node.next is None:
return
while new_next.next is not None:
new_next = new_next.next
new_next.prev.next = None

# delete any node from a doubly linked list
def deleteAnyElement(self,node):
print("The list is empty")
return
if node is None:
print("There is no node to delete")
return

# if the node to deleted is NOT the last node, then  only change next
if node.next is not None:
node.next.prev = node.prev

# if node to be deleted is NOT the head node, then only change the prev
if node.prev is not None:
node.prev.next = node.next
```

The first step is to create an instance of the doubly_linked_list class. Then subsequently call the method to create the elements. Finally, we call the traverseDoublyLinkedList method that traverses through the entire linked list and prints all the elements.

```doubly_list =doubly_linked_list()
doubly_list.createNewElement(12)
doubly_list.createNewElement(8)
doubly_list.createNewElement(62)

As opposed to a singly linked list, here, you do not need to store a reference to the previous node when performing operations such as insert and delete. This is because, from the current node, the user can get a reference to the previous node. As a result, there is no traversing to the previous node and storing its reference the way we do in singly-linked lists.

Doubly linked lists can be traversed both ways. The next node’s reference allows for traversing and searching a linked list in a forward direction. On the other hand, a reference to the previous node is needed to traverse the linked list in a backward direction.

Additional steps are needed to perform operations such as deletion and insertion.

In each node, there is an additional reference that needs to be stored. This adds up to the memory consumption when dealing with doubly-linked lists.

### Conclusion

Do not forget the advantages we mentioned concerning a doubly linked list over a singly linked list. Most critical is the ability to traverse without storing references to the previous node and the next node in a doubly-linked list.

Besides, this ability to traverse back and forth in a doubly-linked list makes it very easy to carry out operations such as deletion and insertion.