Dijkstra's Algorithm Explained

Learn Dijkstra's Algorithm Explained with practical examples, explanations, best practices, and real-world coding techniques.

Dijkstra's algorithm is a shortest path algorithm used to find the minimum-cost path from a starting vertex to other vertices in a weighted graph. It is especially useful when edge weights are non-negative.

The algorithm is widely used in problems involving maps, computer networks, routing, transportation, and other systems where moving from one point to another has an associated cost.

Dijkstra's algorithm uses a greedy strategy. At each step, it selects the unprocessed vertex with the smallest known distance from the source and uses that vertex to improve the distances of its neighbors.

When Should You Use Dijkstra's Algorithm?

Dijkstra's algorithm is appropriate when the graph has weighted edges and all edge weights are non-negative.

Graph Property Dijkstra
Weighted graph Yes
Non-negative edge weights Required
Negative edge weights Not suitable
Single source shortest paths Yes

Example Graph

Consider the following weighted graph:

       4
  A -------- B
  |          |
 2|          |5
  |          |
  C -------- D
       3

If A is the starting vertex, Dijkstra's algorithm calculates the minimum cost required to reach B, C, and D from A.

Core Idea Behind Dijkstra's Algorithm

The algorithm maintains a distance value for every vertex. Initially, the source has distance 0, while every other vertex is assigned Infinity.

A = 0
B = Infinity
C = Infinity
D = Infinity

Whenever a vertex is selected, the algorithm checks whether traveling through that vertex produces a shorter route to each neighbor.

This process is called edge relaxation.

Edge Relaxation

Suppose the current shortest known distance to A is 5 and there is an edge from A to B with weight 3.

distance[A] = 5
weight(A, B) = 3

candidate = distance[A] + weight(A, B)
          = 5 + 3
          = 8

If B currently has a distance greater than 8, its distance is updated to 8. The algorithm can also store A as B's predecessor so the complete route can later be reconstructed.

How Dijkstra's Algorithm Works

  1. Set the source vertex distance to 0.
  2. Set every other vertex distance to infinity.
  3. Select the unvisited vertex with the smallest known distance.
  4. Check all edges leaving the selected vertex.
  5. Relax each neighboring vertex if a shorter route is found.
  6. Mark the selected vertex as processed.
  7. Repeat until the required vertices have been processed.

Step-by-Step Example

Consider this graph:

A -- 4 -- B
|         |
2         3
|         |
C -- 1 -- D

Start at A:

A = 0
B = Infinity
C = Infinity
D = Infinity

Process A. Its neighbors are B and C:

A → B = 4
A → C = 2

Distances:
A = 0
B = 4
C = 2
D = Infinity

C now has the smallest unprocessed distance, so process C next. The route from C to D costs 1:

A → C → D
2 + 1 = 3

Distances:
A = 0
B = 4
C = 2
D = 3

D now has the smallest unprocessed distance. Therefore, the shortest distance from A to D is 3 through C.

Basic JavaScript Implementation

A straightforward implementation can search through all vertices to find the next vertex with the smallest distance.

function dijkstra(graph, start) {
  const distances = {};
  const visited = new Set();

  for (const vertex of Object.keys(graph)) {
    distances[vertex] = Infinity;
  }

  distances[start] = 0;

  while (visited.size < Object.keys(graph).length) {
    let current = null;
    let smallestDistance = Infinity;

    for (const vertex of Object.keys(graph)) {
      if (
        !visited.has(vertex) &&
        distances[vertex] < smallestDistance
      ) {
        smallestDistance = distances[vertex];
        current = vertex;
      }
    }

    if (current === null) {
      break;
    }

    visited.add(current);

    for (const edge of graph[current]) {
      const { node: neighbor, weight } = edge;

      const candidateDistance =
        distances[current] + weight;

      if (candidateDistance < distances[neighbor]) {
        distances[neighbor] = candidateDistance;
      }
    }
  }

  return distances;
}

const graph = {
  A: [
    { node: "B", weight: 4 },
    { node: "C", weight: 2 }
  ],
  B: [
    { node: "D", weight: 3 }
  ],
  C: [
    { node: "D", weight: 1 }
  ],
  D: []
};

console.log(dijkstra(graph, "A"));

The result gives the minimum distance from A to every reachable vertex.

Dijkstra Using a Priority Queue

The simple implementation repeatedly scans every vertex to find the smallest distance. For large graphs, a priority queue implemented with a min-heap is generally more efficient.

The priority queue stores vertices according to their current shortest known distance. The vertex with the smallest distance can then be retrieved efficiently.

Priority Queue

(distance, vertex)

(0, A)
(2, C)
(4, B)
(3, D)

In practical implementations, the priority queue may contain multiple entries for the same vertex. When an outdated entry is removed, it can be ignored if its distance is greater than the current recorded distance.

Priority Queue Implementation

class MinHeap {
  constructor() {
    this.heap = [];
  }

  push(item) {
    this.heap.push(item);
    this.bubbleUp(this.heap.length - 1);
  }

  pop() {
    if (this.heap.length === 0) {
      return null;
    }

    const root = this.heap[0];
    const last = this.heap.pop();

    if (this.heap.length > 0) {
      this.heap[0] = last;
      this.bubbleDown(0);
    }

    return root;
  }

  bubbleUp(index) {
    while (index > 0) {
      const parent = Math.floor((index - 1) / 2);

      if (this.heap[parent][0] <= this.heap[index][0]) {
        break;
      }

      [this.heap[parent], this.heap[index]] =
        [this.heap[index], this.heap[parent]];

      index = parent;
    }
  }

  bubbleDown(index) {
    const length = this.heap.length;

    while (true) {
      let smallest = index;
      const left = index * 2 + 1;
      const right = index * 2 + 2;

      if (
        left < length &&
        this.heap[left][0] < this.heap[smallest][0]
      ) {
        smallest = left;
      }

      if (
        right < length &&
        this.heap[right][0] < this.heap[smallest][0]
      ) {
        smallest = right;
      }

      if (smallest === index) {
        break;
      }

      [this.heap[index], this.heap[smallest]] =
        [this.heap[smallest], this.heap[index]];

      index = smallest;
    }
  }

  isEmpty() {
    return this.heap.length === 0;
  }
}

function dijkstra(graph, start) {
  const distances = {};

  for (const vertex of Object.keys(graph)) {
    distances[vertex] = Infinity;
  }

  distances[start] = 0;

  const minHeap = new MinHeap();
  minHeap.push([0, start]);

  while (!minHeap.isEmpty()) {
    const [currentDistance, current] = minHeap.pop();

    if (currentDistance > distances[current]) {
      continue;
    }

    for (const edge of graph[current]) {
      const { node: neighbor, weight } = edge;

      const newDistance =
        currentDistance + weight;

      if (newDistance < distances[neighbor]) {
        distances[neighbor] = newDistance;
        minHeap.push([newDistance, neighbor]);
      }
    }
  }

  return distances;
}

const graph = {
  A: [
    { node: "B", weight: 4 },
    { node: "C", weight: 2 }
  ],
  B: [
    { node: "D", weight: 3 }
  ],
  C: [
    { node: "B", weight: 1 },
    { node: "D", weight: 1 }
  ],
  D: []
};

console.log(dijkstra(graph, "A"));

Reconstructing the Shortest Path

Sometimes knowing the shortest distance is not enough. For example, a navigation system needs to know which locations should be visited.

Store the previous vertex whenever a shorter distance is discovered:

previous[B] = A;
previous[C] = A;
previous[D] = C;

If the destination is D, following the previous references gives:

D ← C ← A

Reverse:

A → C → D

Complete Path Reconstruction Example

function shortestPath(graph, start, target) {
  const distances = {};
  const previous = {};
  const visited = new Set();

  for (const vertex of Object.keys(graph)) {
    distances[vertex] = Infinity;
    previous[vertex] = null;
  }

  distances[start] = 0;

  while (visited.size < Object.keys(graph).length) {
    let current = null;
    let smallestDistance = Infinity;

    for (const vertex of Object.keys(graph)) {
      if (
        !visited.has(vertex) &&
        distances[vertex] < smallestDistance
      ) {
        smallestDistance = distances[vertex];
        current = vertex;
      }
    }

    if (current === null) {
      break;
    }

    visited.add(current);

    for (const edge of graph[current]) {
      const { node: neighbor, weight } = edge;
      const newDistance =
        distances[current] + weight;

      if (newDistance < distances[neighbor]) {
        distances[neighbor] = newDistance;
        previous[neighbor] = current;
      }
    }
  }

  if (distances[target] === Infinity) {
    return null;
  }

  const path = [];
  let current = target;

  while (current !== null) {
    path.push(current);
    current = previous[current];
  }

  path.reverse();

  return {
    distance: distances[target],
    path
  };
}

const graph = {
  A: [
    { node: "B", weight: 4 },
    { node: "C", weight: 2 }
  ],
  B: [
    { node: "D", weight: 5 }
  ],
  C: [
    { node: "B", weight: 1 },
    { node: "D", weight: 1 }
  ],
  D: []
};

console.log(shortestPath(graph, "A", "D"));

Time Complexity

The complexity of Dijkstra's algorithm depends heavily on how the graph and priority queue are implemented.

Implementation Time Complexity
Simple array / vertex scan O(V²)
Binary heap + adjacency list O((V + E) log V)
Space complexity O(V) auxiliary, excluding graph storage

Here, V represents the number of vertices and E represents the number of edges. With an adjacency list and a binary heap, Dijkstra's algorithm is generally suitable for sparse graphs.

Why Non-Negative Weights Matter

Dijkstra's algorithm depends on a greedy property: after selecting the unprocessed vertex with the smallest distance, its distance can be finalized when all edge weights are non-negative.

A negative edge can invalidate that assumption because a later route may reduce the distance of a vertex that was already considered finalized.

A → B = 4
A → C = 2
C → B = -5

For graphs containing negative edge weights, consider Bellman-Ford instead.

Dijkstra vs BFS

Both algorithms can find shortest paths, but they are designed for different graph conditions.

Feature BFS Dijkstra
Typical graph Unweighted Weighted
Different edge weights No Yes
Negative weights Not applicable No
Typical complexity O(V + E) O((V + E) log V) with a binary heap

Dijkstra vs Bellman-Ford

Both algorithms solve the single-source shortest path problem, but their handling of edge weights differs.

  • Dijkstra requires non-negative edge weights.
  • Bellman-Ford can handle negative edge weights.
  • Bellman-Ford can detect reachable negative-weight cycles.
  • Dijkstra is generally faster for graphs that satisfy its non-negative-weight requirement.

Applications of Dijkstra's Algorithm

Navigation Systems

Road networks can be represented as weighted graphs where edge weights represent distance, travel time, or another route cost.

Network Routing

Network nodes can be modeled as vertices and communication costs can be represented by edge weights.

Game Development

Weighted movement between locations can be modeled as a graph to find a minimum-cost route.

Logistics

Transportation networks can use weighted edges to represent distance, time, or transportation cost.

Common Mistakes

  • Using Dijkstra's algorithm when negative edge weights are present.
  • Forgetting to initialize every distance to infinity.
  • Failing to relax edges after selecting a vertex.
  • Confusing the shortest distance with the actual shortest path.
  • Forgetting to track predecessors when path reconstruction is required.
  • Using a simple O(V²) implementation without considering graph size.
  • Failing to ignore outdated priority queue entries in heap-based implementations.
  • Assuming that the path with the fewest edges is always the cheapest path.

Dijkstra's Algorithm in Interview Problems

Dijkstra's algorithm commonly appears in coding interviews as a shortest-path pattern. The problem statement may not explicitly mention the algorithm. Instead, look for clues such as:

  • A graph or network of connected nodes.
  • A cost, distance, time, or weight associated with each connection.
  • A requirement to minimize total cost.
  • Non-negative weights.
  • A source node from which distances must be calculated.

If the problem asks for a minimum-cost route through a weighted graph and all weights are non-negative, Dijkstra's algorithm should be one of the first approaches to consider.

Key Takeaways

  • Dijkstra's algorithm finds shortest paths from a source vertex.
  • It works with weighted graphs containing non-negative edge weights.
  • The algorithm uses a greedy strategy.
  • Edge relaxation is the main operation used to improve distances.
  • A priority queue can make the algorithm more efficient.
  • Predecessor information can be stored to reconstruct the actual shortest path.
  • A simple vertex-scan implementation has O(V²) time complexity.
  • A binary heap with an adjacency list typically provides O((V + E) log V) time complexity.
  • Dijkstra's algorithm should not be used for graphs with negative edge weights.
  • For negative edge weights, Bellman-Ford is a suitable alternative.

Code Examples

function dijkstra(graph, start) {
  const distances = {};
  const visited = new Set();

  for (const vertex of Object.keys(graph)) {
    distances[vertex] = Infinity;
  }

  distances[start] = 0;

  while (visited.size < Object.keys(graph).length) {
    let current = null;
    let smallestDistance = Infinity;

    for (const vertex of Object.keys(graph)) {
      if (
        !visited.has(vertex) &&
        distances[vertex] < smallestDistance
      ) {
        smallestDistance = distances[vertex];
        current = vertex;
      }
    }

    if (current === null) {
      break;
    }

    visited.add(current);

    for (const edge of graph[current]) {
      const { node: neighbor, weight } = edge;

      const candidateDistance =
        distances[current] + weight;

      if (candidateDistance < distances[neighbor]) {
        distances[neighbor] = candidateDistance;
      }
    }
  }

  return distances;
}

const graph = {
  A: [
    { node: "B", weight: 4 },
    { node: "C", weight: 2 }
  ],
  B: [
    { node: "D", weight: 3 }
  ],
  C: [
    { node: "D", weight: 1 }
  ],
  D: []
};

console.log(dijkstra(graph, "A"));
    
function shortestPath(graph, start, target) {
  const distances = {};
  const previous = {};
  const visited = new Set();

  for (const vertex of Object.keys(graph)) {
    distances[vertex] = Infinity;
    previous[vertex] = null;
  }

  distances[start] = 0;

  while (visited.size < Object.keys(graph).length) {
    let current = null;
    let smallestDistance = Infinity;

    for (const vertex of Object.keys(graph)) {
      if (
        !visited.has(vertex) &&
        distances[vertex] < smallestDistance
      ) {
        smallestDistance = distances[vertex];
        current = vertex;
      }
    }

    if (current === null) {
      break;
    }

    visited.add(current);

    for (const edge of graph[current]) {
      const { node: neighbor, weight } = edge;
      const newDistance =
        distances[current] + weight;

      if (newDistance < distances[neighbor]) {
        distances[neighbor] = newDistance;
        previous[neighbor] = current;
      }
    }
  }

  if (distances[target] === Infinity) {
    return null;
  }

  const path = [];
  let current = target;

  while (current !== null) {
    path.push(current);
    current = previous[current];
  }

  path.reverse();

  return {
    distance: distances[target],
    path
  };
}

const graph = {
  A: [
    { node: "B", weight: 4 },
    { node: "C", weight: 2 }
  ],
  B: [
    { node: "D", weight: 5 }
  ],
  C: [
    { node: "B", weight: 1 },
    { node: "D", weight: 1 }
  ],
  D: []
};

console.log(shortestPath(graph, "A", "D"));
    
class MinHeap {
  constructor() {
    this.heap = [];
  }

  push(item) {
    this.heap.push(item);
    this.bubbleUp(this.heap.length - 1);
  }

  pop() {
    if (this.heap.length === 0) {
      return null;
    }

    const root = this.heap[0];
    const last = this.heap.pop();

    if (this.heap.length > 0) {
      this.heap[0] = last;
      this.bubbleDown(0);
    }

    return root;
  }

  bubbleUp(index) {
    while (index > 0) {
      const parent = Math.floor((index - 1) / 2);

      if (this.heap[parent][0] <= this.heap[index][0]) {
        break;
      }

      [this.heap[parent], this.heap[index]] =
        [this.heap[index], this.heap[parent]];

      index = parent;
    }
  }

  bubbleDown(index) {
    const length = this.heap.length;

    while (true) {
      let smallest = index;
      const left = index * 2 + 1;
      const right = index * 2 + 2;

      if (
        left < length &&
        this.heap[left][0] < this.heap[smallest][0]
      ) {
        smallest = left;
      }

      if (
        right < length &&
        this.heap[right][0] < this.heap[smallest][0]
      ) {
        smallest = right;
      }

      if (smallest === index) {
        break;
      }

      [this.heap[index], this.heap[smallest]] =
        [this.heap[smallest], this.heap[index]];

      index = smallest;
    }
  }

  isEmpty() {
    return this.heap.length === 0;
  }
}

function dijkstra(graph, start) {
  const distances = {};

  for (const vertex of Object.keys(graph)) {
    distances[vertex] = Infinity;
  }

  distances[start] = 0;

  const minHeap = new MinHeap();
  minHeap.push([0, start]);

  while (!minHeap.isEmpty()) {
    const [currentDistance, current] = minHeap.pop();

    if (currentDistance > distances[current]) {
      continue;
    }

    for (const edge of graph[current]) {
      const { node: neighbor, weight } = edge;

      const newDistance =
        currentDistance + weight;

      if (newDistance < distances[neighbor]) {
        distances[neighbor] = newDistance;
        minHeap.push([newDistance, neighbor]);
      }
    }
  }

  return distances;
}

const graph = {
  A: [
    { node: "B", weight: 4 },
    { node: "C", weight: 2 }
  ],
  B: [
    { node: "D", weight: 3 }
  ],
  C: [
    { node: "B", weight: 1 },
    { node: "D", weight: 1 }
  ],
  D: []
};

console.log(dijkstra(graph, "A"));
    

Pro Tip

When you see a weighted graph with non-negative edge costs and need the minimum-cost route from a source, think Dijkstra. For large sparse graphs, use an adjacency list with a min-heap or priority queue, and store predecessor information if the actual route must be returned.

🎨 IgKnight Docs

Master HTML, CSS, JS, Python, .NET and more with easy-to-follow lessons and practical code examples.

© 2025 IgKnight Learning Platform. All rights reserved.