Interview Preparation Tailored for Software Engineers
Tags: javascript, beginners, programming, learning
Published: 2024-03-08 04:25:57

As someone studying to become a software engineer, I've noticed certain questions that I believe may come up in interviews as I study. Along with corresponding answers, I plan to occasionally share these insights. Currently, I'm focusing on learning **JavaScript**. Since I've only recently started my studies, most of these questions will likely cover foundational topics. However, it's true that mastering the basics is crucial, isn't it? Let's solidify our foundation with fundamental knowledge and strive for greater heights together!

**Series 1. JavaScript - Technical Questions**

- __Can you explain the **data types** in JavaScript?__

JavaScript has primitive types and object types. Primitive types include number, string, boolean, null, undefined, and symbol. Object types include array, function, object, and etc.

- __What is **hoisting**? How does it work in JavaScript?__

Hoisting is a javaScript behavior where variable and function declarations are moved to the top of their containing scope during compilation. This means variables and functions are allocated memory before the code is executed. For example,

```
console.log(x); undefined

let x = 23; 
console.log(x); // 23
```

This code above demonstrates the impact of hoisting. Despite the variable x not being declared before execution, it can still be referenced on the first line. However, it is evaluated as undefined before a value is assigned to it. this can be interpreted as follows,

```
let x; // variable declaration moves to the top
console.log(x); // undefined

x = 23; // variable assigned
console.log(x); // 23
```
So, the JavaScript handles this phenomenon by hoisting variable and function declarations to the top before executing the code.

- __What are **closures** and why are they important?__

A closure is a combination of a function and the lexical environment within which that function was declared. Closures allow functions to access variables from an outer scope, and they are an important concept in javaScript. For example,

```
function outerFunc() {
  let outerVar = 'I am from outer space';
  
  function innerFunc() {
    console.log(outerVar);
  }
  
  return innerFunc;
}

let closureEx = outerFunc();
closureEx(); // return: "I am from outer space"
```
In the above code, although innerFunc is defined within outerFunc, it can still access outerVar even after outerFunc has finished executing. This is the concept of closures.

- __What is **strict mode** in JavaScript and how do you enable it?__

Strict mode in javaScript enables stricter syntax checking and error handling. It can be activated by adding `"use strict";` at the beginning of a script. For example,

```
// variable not declared
x = 45; // it worked and no error occurred

// function parameters have duplicate names
function duplicateParam(z, z) {
    return z + z;
}
duplicateParam(7, 9); // 18, no error occurred

// using duplicate property names in an object literal
let obj = {
    obj37: 1,
    obj37: 2
}; // no error occurred and it worked
```

In the absence of strict mode, the JavaScript will overlook such mistakes and proceed with executing the code.

```
"use strict"; // strict mode on!

// variable not declared
x = 29; // ReferenceError: x is not defined

// function parameters have duplicate names
function duplicateParam(x, x) {
    return a + a;
}
duplicateParam(3, 6); // SyntaxError: Duplicate parameter name not allowed in this context

// using duplicate property names in an object literal
let obj = {
    obj55: 1,
    obj55: 2
}; // SyntaxError: Duplicate data property in object literal not allowed in strict mode
```
In the example above, we activated strict mode using "use strict";. Now, when we fail to declare variables before using them, duplicate function parameter names, or use duplicate property names in object literals, errors are thrown. Strict mode helps reduce such mistakes and makes the code safer.


- __Difference between **arrays** and **objects**__

Arrays are ordered lists of items accessed by numeric indexes, whereas objects are unordered sets of key-value pairs accessed by keys. Arrays are ideal for storing similar data types, while objects offer more versatility and can store different data types using descriptive keys.

```
// Array
let balls = ['basketball', 'baseball', 'football']; // array declaration

console.log(balls[1]); // return: "baseball"
console.log(balls.length); // return: 3

// Object
let person = { // object declaration
  name: 'Michael',
  age: 41,
  city: 'Baltimore'
};

console.log(person.name); // return: "Michael"
console.log(person.age); // return: 41
```
In simple terms, objects are surrounded by {}, while arrays are surrounded by [].

-  __what is **execution context**?__

Execution context refers to the environment created when JavaScript code is executed. It contains information about the context in which the code is running, including variables, function declarations, and other execution-related details. Execution context is composed of three main components.

1. Variable environment: An object that contains all variables and functions declared within the current context. This object includes mappings of variable identifiers to their corresponding values.

1. Lexical environment: similar to the variable environment, but it also includes information about the outer environment (lexical scope). in other words, it maintains references to the outer context (parent context) of the current context.

1. This binding: represents the object that the this keyword refers to. The value of this is dynamically determined each time an execution context is created.

For example,

```
// Global Execution Context
let globalVar = 'I am global'; // global variable

function outerFunc() {
    // outerFunc context
    let outerVar = 'I am outer'; // outerFunc inner variable

    function innerFunc() {
        // innerFunc context
        let innerVar = 'I am inner'; // innerFunc inner variable
        console.log(globalVar); // return "I am global" 
        console.log(outerVar); // return "I am outer" 
        console.log(innerVar); // return "I am inner"
    }

    innerFunc(); // innerFunc invoke
}

outerFunc(); // outerFunc invoke
```

In the given example, the global context is created first. Then, when outerFunc is called, its execution context is created and added to the call stack. Similarly, when innerFunc is called, a new execution context is created and added to the call stack. Each execution context contains information about variables and functions within that context. For example, innerFunc's execution context includes variables like globalVar, outerVar, and innerVar. These variables are only accessible within their respective contexts. When a context finishes execution, it is removed from the call stack. Therefore, after innerFunc's execution completes, its execution context is popped off the stack, followed by outerFunc's execution context. Finally, the global context is popped off the stack, leaving it empty.


I'll stop the Series 1 here. In the future, as I continue studying, I'll gradually post about things I find important. Let's keep pushing forward!