Table of contents
1.
Introduction
2.
How Does LinkedList Work Internally?
3.
Constructors in the LinkedList
3.1.
LinkedList()
3.2.
LinkedList(Collection<? extends E> c)
4.
Methods for Java LinkedList
4.1.
add(E element)
4.2.
add(int index, E element)
4.3.
get(int index)
4.4.
remove(int index)
4.5.
size()
4.6.
contains(Object o)
4.7.
clear()
5.
Implementation
5.1.
Java
6.
Performing Various Operations on LinkedList
6.1.
Adding elements
6.2.
Updating elements
6.3.
Removing elements
6.4.
To Array()
6.5.
remove First()
6.6.
remove last()
7.
Advantages of using LinkedList in Java
8.
Disadvantages of using LinkedList in Java
9.
Frequently Asked Questions
9.1.
What is the time complexity of inserting or removing elements at the beginning or end of a LinkedList?
9.2.
Can a LinkedList contain duplicate elements?
9.3.
Is it efficient to search for an element in a LinkedList?
10.
Conclusion
Last Updated: Jul 15, 2024
Medium

Linked List in Java

Author Rahul Singh
0 upvote
Career growth poll
Do you think IIT Guwahati certified course can help you in your career?

Introduction

LinkedList is a fundamental data structure in Java that allows for efficient insertion and deletion of elements. It consists of a series of nodes, where each node contains a data value and a reference to the next node in the sequence. Unlike arrays, LinkedLists provide dynamic resizing and better performance for certain operations. 

Linked List in Java

In this article, we will understand the inner workings of LinkedList, its constructors, methods, and various operations such as adding, updating, removing, and iterating over elements. We will also discuss the advantages and disadvantages of using LinkedList in Java.

How Does LinkedList Work Internally?

LinkedList is a linear data structure that consists of a sequence of nodes. Each node in a LinkedList contains two parts: a data value and a reference (or link) to the next node in the sequence. The first node is called the head, and the last node points to null, indicating the end of the list.

Internally, LinkedList maintains three instance variables:

1. head: A reference to the first node in the list.
 

2. tail: A reference to the last node in the list.
 

3. size: An integer representing the number of elements in the list.


When an element is added to the LinkedList, a new node is created with the given data value. The new node's reference is set to the current head, and the head is updated to point to the new node. Similarly, when an element is removed, the references are adjusted to maintain the correct sequence of nodes.

LinkedList provides constant-time O(1) performance for insertion and deletion operations at both ends of the list (head and tail). However, accessing elements at a specific index requires traversing the list from the head or tail, resulting in a linear-time O(n) operation.

Here's a visual representation of a LinkedList:

Flow Diagram of a Linked List

Each node contains a data value and a reference to the next node, forming a chain-like structure.

Constructors in the LinkedList

LinkedList provides several constructors to create and initialize a new instance of the LinkedList class. Here are the available constructors:

LinkedList()

This is the default constructor that creates an empty LinkedList.

   LinkedList<Type> list = new LinkedList<>();

LinkedList(Collection<? extends E> c)

This constructor creates a LinkedList containing all the elements from the specified collection, in the order returned by the collection's iterator.

   List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
   LinkedList<Integer> list = new LinkedList<>(numbers);


These constructors provide flexibility in creating a LinkedList, either empty or initialized with elements from another collection.

Methods for Java LinkedList

LinkedList provides a rich set of methods to perform various operations on the list. Here are some commonly used methods:

add(E element)

Appends the specified element to the end of the list.

   LinkedList<String> list = new LinkedList<>();
   list.add("Apple");
   list.add("Banana");

add(int index, E element)

Inserts the specified element at the specified position in the list.

   list.add(1, "Orange");
   // list now contains ["Apple", "Orange", "Banana"]

get(int index)

Returns the element at the specified position in the list.

   String element = list.get(1);
   // element = "Orange"

remove(int index)

Removes the element at the specified position in the list.

   list.remove(1);
   // list now contains ["Apple", "Banana"]   

size()

Returns the number of elements in the list.

   int size = list.size();
   // size = 2

contains(Object o)

Returns true if the list contains the specified element.

  boolean containsApple = list.contains("Apple");
   // containsApple = true

clear()

Removes all the elements from the list.

   list.clear();
   // list is now empty


These are just a few examples of the methods available in LinkedList. The LinkedList class also provides methods for iterating over elements, converting to an array, removing the first or last element, and more.

Implementation

To implement a LinkedList in Java, we can create a custom class that represents a node and contains methods for adding, removing, and accessing elements.

For example : 

  • Java

Java

public class LinkedList<E> {

   private Node<E> head;

   private Node<E> tail;

   private int size;

   private static class Node<E> {

       E data;

       Node<E> next;

       Node(E data) {

           this.data = data;

           this.next = null;

       }

   }

   public void add(E element) {

       Node<E> newNode = new Node<>(element);

       if (head == null) {

           head = newNode;

           tail = newNode;

       } else {

           tail.next = newNode;

           tail = newNode;

       }

       size++;

   }

   public void add(int index, E element) {

       if (index < 0 || index > size) {

           throw new IndexOutOfBoundsException("Invalid index: " + index);

       }

       if (index == size) {

           add(element);

       } else {

           Node<E> newNode = new Node<>(element);

           if (index == 0) {

               newNode.next = head;

               head = newNode;

           } else {

               Node<E> prev = getNodeAtIndex(index - 1);

               newNode.next = prev.next;

               prev.next = newNode;

           }

           size++;

       }

   }

   public E get(int index) {

       if (index < 0 || index >= size) {

           throw new IndexOutOfBoundsException("Invalid index: " + index);

       }

       return getNodeAtIndex(index).data;

   }

   public E remove(int index) {

       if (index < 0 || index >= size) {

           throw new IndexOutOfBoundsException("Invalid index: " + index);

       }

       Node<E> removedNode;

       if (index == 0) {

           removedNode = head;

           head = head.next;

           if (head == null) {

               tail = null;

           }

       } else {

           Node<E> prev = getNodeAtIndex(index - 1);

           removedNode = prev.next;

           prev.next = removedNode.next;

           if (prev.next == null) {

               tail = prev;

           }

       }

       size--;

       return removedNode.data;

   }

   private Node<E> getNodeAtIndex(int index) {

       Node<E> current = head;

       for (int i = 0; i < index; i++) {

           current = current.next;

       }

       return current;

   }

   public int size() {

       return size;

   }

}
You can also try this code with Online Java Compiler
Run Code


In this implementation, we have a `Node` class that represents each node in the LinkedList. The `LinkedList` class contains methods for adding elements (`add()`), inserting elements at a specific index (`add(int index, E element)`), retrieving elements (`get()`), removing elements (`remove()`), and getting the size of the list (`size()`).

The `getNodeAtIndex()` method is a helper method that traverses the list to find the node at a specific index.

This implementation provides the basic functionality of a LinkedList. You can further extend it by adding more methods like `contains()`, `clear()`, and others based on your requirements.

Performing Various Operations on LinkedList

Let's see how to perform various operations on it:

Adding elements

LinkedList<String> list = new LinkedList<>();

list.add("Apple");
list.add("Banana");
list.add(1, "Orange");
// list now contains ["Apple", "Orange", "Banana"]

Updating elements

list.set(1, "Grapes");
// list now contains ["Apple", "Grapes", "Banana"]

Removing elements

list.remove(1);
// list now contains ["Apple", "Banana"]
String removedElement = list.remove(0);
// removedElement = "Apple"
// list now contains ["Banana"]
Iterating over elements:
for (String element : list) {
    System.out.println(element);
}


Output:

Banana

To Array()

String[] array = list.toArray(new String[list.size()]);
// array now contains ["Banana"]
Size():
int size = list.size();
// size = 1

remove First()

String firstElement = list.removeFirst();
// firstElement = "Banana"
// list is now empty

remove last()

list.add("Apple");
list.add("Orange");
String lastElement = list.removeLast();
// lastElement = "Orange"
// list now contains ["Apple"]

Advantages of using LinkedList in Java

LinkedList have several advantages over other data structures in certain scenarios, like :

1. Dynamic size: LinkedList allows for dynamic resizing, meaning the size of the list can grow or shrink as needed during runtime. This is particularly useful when the number of elements is not known in advance or when frequent additions or removals are required.
 

2. Efficient insertion and deletion: LinkedList provides constant-time O(1) performance for insertion and deletion operations at both ends of the list (head and tail). This makes it efficient for scenarios where elements need to be frequently added or removed from the beginning or end of the list.
 

3. No shifting of elements: Unlike arrays, inserting or removing elements from the middle of a LinkedList does not require shifting the subsequent elements. Each node maintains a reference to the next node, allowing for quick and efficient modifications to the list structure.
 

4. Flexibility: LinkedList can be used to implement various data structures such as stacks, queues, and associative arrays. Its flexibility allows for creative problem-solving and adapting to different requirements.
 

5. Memory efficiency: LinkedList allocates memory for each node dynamically, which can be more memory-efficient compared to arrays when dealing with large datasets or when the size of the list is not known in advance.

Disadvantages of using LinkedList in Java

Now, let’s discuss some of the disadvantages of Linkedlist : 

1. Random access is slow: Accessing an element at a specific index in a LinkedList requires traversing the list from the head or tail until the desired index is reached. This results in a linear-time O(n) operation, making random access inefficient compared to arrays, which provide constant-time O(1) access to elements by index.
 

2. Extra memory usage: LinkedList requires extra memory to store the references (pointers) to the next node for each element. This overhead can be significant when storing small elements or when memory is limited. In contrast, arrays store elements contiguously in memory, which can be more memory-efficient.
 

3. Lack of cache-friendly operations: Arrays have better cache locality because elements are stored contiguously in memory. This allows for efficient cache utilization and faster element retrieval. LinkedList, on the other hand, may have nodes scattered in different memory locations, leading to cache misses and slower performance for certain operations.
 

4. Overhead for small lists: For small lists or scenarios where the number of elements is known and fixed, using a LinkedList may introduce unnecessary overhead compared to using an array. The additional memory for references and the lack of cache-friendly access can make LinkedList less efficient in such cases.
 

5. Complexity of implementation: Implementing a LinkedList from scratch requires more code and is more complex compared to using an array. The additional complexity can make the code harder to read, understand, and maintain, especially for beginners.

Frequently Asked Questions

What is the time complexity of inserting or removing elements at the beginning or end of a LinkedList?

The time complexity of inserting or removing elements at the beginning or end of a LinkedList is O(1) or constant time.

Can a LinkedList contain duplicate elements?

Yes, a LinkedList can contain duplicate elements. It allows for multiple occurrences of the same value.

Is it efficient to search for an element in a LinkedList?

 Searching for an element in a LinkedList requires traversing the list from the beginning, resulting in a time complexity of O(n) in the worst case. It is not as efficient as searching in an array or other data structures that provide random access.

Conclusion

In this article, we have learned about the LinkedList data structure in Java. We discussed its internal workings, constructors, and methods. We also looked at how to implement a LinkedList and perform various operations such as adding, updating, removing, and iterating over elements. Additionally, we discussed the advantages and disadvantages of using LinkedList in different scenarios.

You can also practice coding questions commonly asked in interviews on Coding Ninjas Code360

Also, check out some of the Guided Paths on topics such as Data Structure and AlgorithmsCompetitive ProgrammingOperating SystemsComputer Networks, DBMSSystem Design, etc., as well as some Contests, Test Series, and Interview Experiences curated by top Industry Experts.

Live masterclass