Updating HTML table (Userlist) with Node.js
Your code looks quite clear, and I see only some possible improvements.
Regarding your questions:


I don't think it'd better to split into fetchRoomList and updateRoomList separate functions: there are a few common vars that should be computed twice; additionally it'd prevent increasing performance like I suggest below (by traversing HTML tree less times).
using pure JS instead of jQuery would obviously increase performance a bit, but IMO it doesn't worth the negative impact on readability.


Then I can suggest what follows:


caching jQuery traversal:


currently you're looking:


for .room-X once for each room
for .lobby-container .room-list tbody once for each room to add a &lt;tr class="room-X"&gt; (when creating row)
for .lobby-container .room-list tbody .XXX, where XXX is .room-players (once), then .room-btn .btn (twice).

So to create C rows and update U rows you spend 2xC + 4xU "big" traversals for each room.
Instead you can:


first get var $rooms = $('.lobby-container .room-list tbody'); once for all
then look for $room = $(#${id}, $rooms); once for each room
and use $rooms.prepend() when creating row or $('XXX', $room) when updating room (these are only "little" traversals, since inside of $rooms).

this way you spend only 1 "big" traversal, plus 0xC + 3xU "little" traversals.
Note that room-X is unique so to be more strictly significant you might change the class into id.

less important: you also can cache r.id (used several times)
factorizing operations when creating room:


depending on r.players.length &gt;= r.slots you execute different but analogue operations on the room, using twice the same jQuery traversal
instead you can get the condition var isFull = r.players.length &gt;= r.slots;, then use it in each operation, all operations being attached to the same unique traversal
this way you actually spend only 1 "big" traversal, plus 0xC + 2xU "little" traversal

Last point, I suggest using tagged strings for row creation, which at the same time reduces code and improves readability. You'll notice I also choosed to omit the closing tags &lt;/tr&gt; and &lt;/td&gt; (optional in HTML5).


Here is the whole refactored function:

socket.on( 'updateRoomsList', function (room_list) {
  var $rooms = $('.lobby-container .room-list tbody');
  for (var i = room_list.length - 1; i &gt;= 0; i -= 1) {
    var r = room_list[i],
        id = `room-${r.id}`,
        $room = $(`#${id}`, $rooms);
    if (!$rooms.length) {
      // Create row
      $rooms.prepend(`
&lt;tr id="${id}"&gt;
  &lt;td&gt;${r.id}
  &lt;td&gt;${r.name}
  &lt;td class="room-players"&gt;${r.players.length}/${r.slots}
  &lt;tdclass="room-button"&gt;
    &lt;a class="btn btn-block btn-success btn-xs" onclick="joinRoom(${r.id});"&gt;
      JOIN
    &lt;/a&gt;
        `);
    } else {
      // Update row
      $('.room-players', $room).text(`${r.players.length}/${r.slots}`);
      var isFull = r.players.length &gt;= r.slots;
      $('.room-button .btn', $room)
        .text(isFull ? 'FULL' : 'JOIN')
        .toggleClass('btn-warning disabled', isFull)
        .toggleClass('btn-success', !isFull);
    }
  }
}