Javascript solution. Detailed Explanation
\n/*\n  Goal is to find edges that don\'t lead to a cycle.\n\n  rank   - time stamp of your dfs traversal\n  minObs - without retraversing through visited node 
```\n/*\n  Goal is to find edges that don\'t lead to a cycle.\n\n  rank   - time stamp of your dfs traversal\n  minObs - without retraversing through visited node (but you will "peek" to observe the rank of visited node)\n           lowest rank observed on that path (path ends when it hits a leaf or a dead end (all children are already visited))\n\n  You will have a cycle if a child\'s minObs is lower than or equal to rank (example, if child\'s minObs is equal to the nodes rank\n  that it hit itself on the way, thus a cycle. lower minObs would mean it would eventually lead back to the node itself, so same meaning)\n  \n  so mark your graph with rank and along the way keep track of minObs for each node. if node.rank > child.minObs, edge between node and child\n  is a bridge since that edge will never lead back to the node itself along the way.\n*/\n\nconst criticalConnections = (n, connections) => {\n  // need graph to traverse\n  const graph = createGraph(n, connections);\n\n  // so you never retraverse visited path. if child was visited and IS NOT a direct parent, you will\n  // end up peeking for its value to keep track of minObs in the way\n  const visited = new Set();\n  let   rank  = 0;\n\n  // this will be your output. if condition meets, edge will be pushed here\n  const output = [];\n\n  // dfs function in this scope so variable rank is easily updated\n  function dfs(node, parent) {\n    visited.add(node.val);\n\n    // by default, minObs will be at the greatest its own rank\n    node.rank = rank;\n    node.minObs = rank;\n\n    // increment rank for next nodes\n    rank++;\n\n    node.children.forEach((child) => {\n      // don\'t revisit your parent\n      if (child === parent) { return; }\n      if (visited.has(child)) {\n        // if node visited, just peek the value\n        node.minObs = Math.min(node.minObs, graph[child].minObs);\n        return;\n      } else {\n        // traverse and also update minObs so its parent can use it later\n        node.minObs = Math.min(node.minObs, dfs(graph[child], node.val));\n      }\n\n      // condition explained in the comments above\n      if (node.rank < graph[child].minObs) output.push([node.val, child]);\n    });\n\n    return node.minObs;\n  };\n\n  dfs(graph[connections[0][0]], connections[0][0]);\n\n  return output;\n};\n\n\nfunction createGraph(n, connections) {\n  const output = {};\n\n  for (let i = 0; i < n; i++) {\n    output[i] = { val: i, children: [], rank: -Infinity, minObs: Infinity };\n  }\n\n  connections.forEach((connection) => {\n    output[connection[0]].children.push(connection[1]);\n    output[connection[1]].children.push(connection[0]);\n  });\n\n  return output;\n}\n\n```
['Depth-First Search', 'JavaScript']