JavaScript is a synchronous, single-threaded language. It can only execute one line at a time.
The Call stack is a Last In First Out data structure and it is where all the code gets executed. This maintains the order of execution.
Everything in JavaScript executes inside the Global Execution Context. Global execution Context is created in two phases:
Memory allocation of variables and functions happens in this phase. Once the variable is created, the value will be assigned as undefined (just a temporary placeholder) and all the functions will point to its code. Due to this phase, you can access all the variables in JavaScript even before you have initialized it and it will not throw any error. This concept is popularly called Hoisting.
All the variable values are initialized according to their assignments and code will be executed in this phase
Whenever a function is invoked, a brand new execution context and lexical environment are created. The lexical environment is the local memory along with the lexical environment of a parent.
Once all the code execution is completed, the global execution context will be deleted.
There are different APIs provided by the Web Browser like setTimeout(), setInterval(), clearTimeout(), clearInterval(), etc that help to perform additional tasks that cannot be run using the main thread.
When the browser is done with the timer (executed by Web APIs), it doesn't transfer the callback function to the call stack immediately instead it will send the callback function to the callback queue.
The Callback queue keeps track of all the function queues, which are needed to be pushed into the call stack. The queue data structure is required to maintain the correct sequence in which all operations should be sent for execution.
Microtask Queue is like the Callback Queue but Microtask Queue has higher priority. All the callback functions coming through Promises and Mutation Observer will go inside the Microtask Queue.
The event loop continuously monitors the call stack and callback queue/microtask queue. If the call stack is empty and if the event loop sees the function that is waiting to be executed in the callback queue, the event loop just takes that function and puts it in the call stack then the callback function will be quickly executed. It specifically manages the execution of asynchronous callbacks.