Loop Variable Issue with Streaming Platform API Requests

I’m having trouble with a JavaScript loop when working with a streaming platform’s API. My code checks if streamers are live or offline. For live streamers, I use one API endpoint. For offline streamers, I make another API call to get their channel info.

The problem is weird. When streamers are offline, the loop only processes the last streamer from my array instead of all the offline ones. I can’t figure out why this happens.

var makeRequest = function(endpoint, callback) {
    var request = new XMLHttpRequest();
    request.open("GET", endpoint, true);
    request.onload = function() {
        if(request.readyState == 4 && request.status == "200") {
            callback(JSON.parse(request.responseText));
        }
    };
    request.send();
};

var streamers = ["ninja", "shroud", "pokimane", "xqc", "tfue", "sodapoppin"];
var api_key = "abc123def456ghi789jkl";
var container = document.getElementById("streamer-list");

var elements = [];
for(var j = 0; j < streamers.length; j++) {
    var live_url = "https://api.example.tv/v1/live/" + streamers[j] + "?key=" + api_key;
    var profile_url = "https://api.example.tv/v1/users/" + streamers[j] + "?key=" + api_key;

    makeRequest(live_url, function(data) {
        if( data["live_stream"] !== null ) {
            elements[j] = document.createElement("div");
            elements[j].className = "stream-card";
            elements[j].innerHTML = "<a href='" + data.live_stream.profile["link"] + 
                                        "'><img src='" + 
                                        data.live_stream.profile["avatar"] + 
                                        "' /><h3>" + 
                                        data.live_stream.profile["username"] + 
                                        "</h3><span>" + 
                                        data.live_stream["category"] + "</span></a>";
            container.appendChild(elements[j]);
        } else {
            makeRequest(profile_url, function(profile) {
                elements[j] = document.createElement("div");
                elements[j].className = "stream-card";
                elements[j].innerHTML = "<a href='" + profile["link"] + 
                                            "'><img src='" + 
                                            profile["avatar"] + 
                                            "' /><h3>" + 
                                            profile["username"] + 
                                            "</h3><span>Not Live</span></a>";
                container.appendChild(elements[j]);
            });
        }
    });
}

Any ideas what’s causing this behavior?

ahh the old closure gotcha strikes again! happens to everyone eventually. quick fix is just change var j to let j in your for loop and you’re good to go. the let keyword creates block scope so each callback remembers it’s own j value instead of sharing the same one

You’re dealing with a classic JavaScript closure trap that catches a lot of developers. The root cause is that all your callback functions are sharing the same reference to the loop variable j, which by the time any callback executes has already reached its final value. I encountered this exact scenario while building a multi-streamer dashboard last year and it drove me crazy for hours. The simplest fix is changing var j to let j in your for loop declaration. This creates a new lexical scope for each iteration, so each callback gets its own isolated copy of the index variable. Another approach would be using forEach with the streamers array instead of a traditional for loop, which naturally provides the scope isolation you need. Both methods will ensure each callback references the correct streamer index rather than just the last one.

This is a typical asynchronous JavaScript closure issue. When your callbacks execute, the loop has already completed and the variable j holds its final value. To fix this, you need to create a new scope for each iteration. Replace your for loop with for(let j = 0; j < streamers.length; j++) - using let creates block scope so each callback gets its own copy of j. Alternatively, you could wrap the makeRequest calls in an immediately invoked function expression that passes j as a parameter. I ran into this exact same problem when building a similar dashboard and the let solution worked perfectly.

classic closure problem mate! the variable j gets captured by reference in your callbacks, so by the time the async requests finish, the loop is done and j equals the last index. try wrapping your makeRequest calls in an IIFE or use let instead of var for block scoping.