Front End Engineer Questions that you should know
Dec 10, 2022 - 20 min read
Front End Engineer Questions that you should know
Front-end web development is an exciting field that involves creating and building the user-facing elements of a website or application. As a front-end developer, you are responsible for bringing a designer's vision to life and ensuring that the website or application is visually appealing and user-friendly. These questions cover various topics, including HTML, CSS, and JavaScript. Practising and familiarising yourself with these questions can increase your confidence and chances of landing your dream job.
Table of Contents
- Know how to manipulate DOM API's, querying the structure of a DOM
- Refresh your knowledge of HTTP protocol
- Learn how does JS work under the hood
- Types
- Scope
- Objects
- Asynchronous programming
- Apply(), call(), bind()
- Extra
DOM API
// QUERY SELECTOR const list = document.querySelector(''#-id'); const listItem = document.querySelector('.classname'); document.querySelectorAll('li:nth-child(odd)') // CSS added;
// CHILDREN & PARENTS let val; val = list.children[1].children; // get the parent node val = listItem.parentElement;
// CREATE ELEMENT const li = document.createElement('li'); li.className = 'collection-item'; li.setAttribute('title', 'New Item'); li.appendChild(document.createTextNode('Hello World')); const link = document.createElement('a'); document.querySelector('ul.className').appendChild(li);
// REPLACE const newHeading = document.createElement('h2'); // Create a text node newHeading.appendChild(document.createTextNode('Change Text')); const oldHeading = document.querySelector('.className'); const cardAction = document.querySelector('.ParentElement'); cardAction.replaceChild(newHeading, oldHeading); // DELETE const lis = document.querySelector('#id or .className'); lis.remove(); lis.removeChild(doc..[0]);
lis.addEventListener('click', (e) => { e.preventDefault(); if (e.target.parentElement.classList.contains('delete-item')) { console.log('delete item'); e.target.parentElement.parentElement.remove(); } });
HTTP Protocol
HTTP - hypertext transfer protocol, used to transfer hypertext files from the client to the server
HTTP Requests:
- GET: retrieve data from a specified resource
- POST: submit data to be processed to a specific resource
- PUT: update a specified resource
- DELETE: delete a specified resource
- HEAD: same as GET but does not return the body
- OPTIONS: Returns the supported HTTP methods
- PATCH: update partial resources
How does javascript work under the hood
JavaScript is a single-threaded, non-blocking, asynchronous and concurrent language.
JS engine consists of the following:
- Memory Heap (memory allocation const a = 1)
- Call stack (code is read & executed) = data structure which records where in a program we are. LIFO, because it is a compiled language, or parsed, there is some processing step that has to happen before execution to set up lexical environment Scope
- WebAPIs (DOM API, Asynchronous programming,
setTimeout()
) = it disappears from the call stack into the task queue, then through event loop back to the stack.
Event loop - a tool which looks at the stack and task queue, if the stack is empty, it pushes the first task into the stack.
For more reference, check out this video, its the best 🤓
Types
In types
we have three topics:
- values & references of primitive types and objects.
- let, const and var = Variables
- Typescript (optional)
Values & References of primitive types and objects
5 primitive Data types: Number, Boolean, null, undefined, String - primitive types 3 Object Data types: Array, Function & Object
Primitive value types
Primitive types contain value. When we assign variable x
to new variable y
, we copy the value to the new variable. They have no relationship with each other.
const x = 10; const y = x;
Object value types
Variables with non-primitive types are assigned a reference to that value. Apart from value, it also sets an address - data type which points to the location in memory of a value that is passed by reference. When we assign a variable ref = refCopy, its the address or the reference that is being copied
== & === operators
Equality operators used on Objects only check the reference. Comparing two distinct objects with the same properties with the equality operator is incorrect.
Passing parameters through functions
If it is primitive, the value is copied into the parameter.
const hundred = 100; const two = 2; const multiply = (x, y) => { return x * y; }; const twoHundred = multiply(hundred, two);
Pure Function
A function that only accepts primitive values into its parameters and does not affect the outside scope.
Impure function, when passing Object as parameter
A function that takes in an Object can mutate the state of its surrounding scope (ie mutate the Object). When taking an Object, write pure functions!
let, const and var
var
= scoped to the function or, if outside of the function, scoped to the global objectconst
andlet
= are block-scoped, meaning they are only accessible within the nearest set of curly braces (function, if-else statement, for loop). If there is nolet
&const
, block {} scope is not created.var
allows variables to be hoisted,const
andlet
will throw an errorvar
allows to redeclare the variable,let
andconst
will throw an errorlet
allows reassigning the variable value,const
causes an exception error
problem with const = it allows to mutate the value of array const x = [1, 2]; x[1] = y
Typescript
There are several types in TypeScript, including basic types such as numbers, strings, and booleans, and more complex types such as arrays and objects. One of the basic types in TypeScript is the number type, which is used to represent numeric values. This type can be used for both integers and floating-point numbers. Unique values, such as Infinity and NaN, can describe mathematical operations resulting in undefined or infinite values. Another basic type in TypeScript is the string type, which represents text values. The boolean type is another basic type in TypeScript, and it is used to represent logical values such as true or false. This type is often used in conditional statements and loops and can also store the result of a comparison operation. In addition to basic types, TypeScript supports more complex types, such as arrays and objects. Arrays are used to store collections of values, which are declared using square brackets. For example, the following code creates an array of numbers:
let numbers: number[] = [1, 2, 3, 4, 5];
Objects are used to store collections of related values, and they are declared using curly braces. Each property in an object has a name and a value, and you can access the properties of an object using dot notation. For example, the following code creates an object with three properties:
let person: object = { firstName: 'John', lastName: 'Doe', age: 30 };
In addition to these basic and complex types, TypeScript supports several other types, such as tuples, enums, and any. Tuples allow you to store a fixed-size array of values, where each value can have a different type. Enums allow you to create a set of named constants, and the any type enables you to opt-out of type checking for a variable.
Scope
Scope defines where the JS compiler looks for things like variables and functions. It is a set of rules that determines where and how a variable can be accessed in your code.
Lexical scope is a reference to JS on where to look for things, fixed at compile time ahead of execution.
Why? JS processes a scope & puts identifiers to recognise their position and how to execute them afterwards properly. Lexical scope is determined at compile time; once it runs through, it stays the same.
Why do we need scope?
- Avoid name collisions
- Security, extra layer against misuse
- Protect yourself for future refactoring
Think of lexical scope as a building, going from the ground floor all the way to the top (global scope). It is a set of rules that determines where and how a variable can be accessed in your code. For example:
const teacher = 'Ilya'; // global scopefunction otherClass() { // function scope const teacher = 'Ilya'; function ask(question) { console.log(teacher, question); } ask('Why?'); } otherClass(); // Ilya Why? ask('Why?'); // ReferenceError: ask is not defined
The scope of a variable is the region of your program in which you can access the variable, by its identifier, in which it is declared. In JavaScript, each function creates a new scope. Variables defined inside a function are not accessible (visible) from outside the function. For more reference on the topic of scope, check out this book by Kyle Simpson. He is a fantastic author who helped me understand the topic of scopes once and for all.
Historical bad part of JS: auto-global variables; if you try to assign a variable that's never been formally declared, JS auto-creates it in the global scope. NEVER DO IT!
Variables are either a target position of an assignment(receiving it), or retrieving it (source position)
Hoisting
Hoisting - convenience term which describes the lexical scope behaviour in which variable declaration is being moved up to the top of their module/function-level scope. However, only the declaration is moved, not the assignment of variable value.
console.log(foo); // undefined var foo = 1; console.log(foo); // 1
A function declaration is being hoisted, not the function expression! When you define a function expression to a variable, the variable's declaration is hoisted, but the assignment happens at runtime
// DECLARATION console.log(foo); // Function: foo foo(); // 'Fugazi' function foo { console.log('Fugazi') } // EXPRESSION console.log(foo); // undefined foo(); // TypeError: foo is not a function const foo = () => { console.log('Fugazi') }
'let' and 'const' are hoisted as well, but accessing them before the declaration will give ReferenceError, you need to initialise them first.
Closure
Closure is a function that remembers its lexical scope even when the function is executed outside that lexical scope. It is a function that has access to the parent scope, even after the parent function has closed.
It is a key 🔑 building block for JS, which makes it so functional. ( ͡° ͜ʖ ͡°)_/¯
Closure is when a function can remember and access its lexical scope, the variables outside of itself, even when the outer function executes outside that scope.
Closure - is an inner function within a function that has access to the outer (enclosing) function's variables and parameters- via a scope chain.
Closure has three scope chains:
- access to its own scope
- access to enclosing function scope
- access to global variables
To use a closure, define a function inside another function and expose it. To expose a function, return it or pass it to another function.
function Counter(start) { const count = start; return { increment: () => {count++}, get: () => {return count}; } } const foo = Counter(4); foo.increment(); foo.get(); // 5
Since it is impossible to reference or assign scopes in JS, there is no way of accessing count
from the outside.
Closure is not capturing a value; it preserves access to variables.
Rules:
- Inner function still has access to the variables in the outer function even after the outer function finished executing
- Closures store references to the outer function variables; they do not store the actual value. Just like const a = const b Values & References of primitive and objects
- Because closures have access to updated values of outer variables, it can lead to bugs using a for loop.
for (var i = 0; i < 10; i++) { setTimeout(() => { console.log(i); }, 1000); } // will print 10 ten times.
Anonymous arr Fn keeps a reference to i, at the time console.log
is called, the for loop
has already finished. To fix this, use an anonymous wrapper IIFE or initialise let
:
for (var i = 0; i < 10; i++) { ((e) => { setTimeout(() => { console.log(e); }, 1000); })(i); } for (let i = 0; i < 10; i++) { setTimeout(() => { console.log(i); }, 1000); }
Anonymous Fn get called immediately with i
and will receive a copy of i
as its parameter e.
Assume that closure is a scope-based operation.
Use case:
Used for object data privacy, in event handlers and callback functions
💡 React: useEffect. It can lead to stale closure - variable falling out of sync.
💡 Nodejs: asynchronous non-blocking code functions
Some additional reading on closures.
Modules
Modules encapsulate data and behaviour methods together. The state (data) is held (private) by its methods via closure.
JavaScript modules are a way to include and reuse code in your applications. They can help you organise your code, prevent naming conflicts, and make it more maintainable.
There are two main types of modules in JavaScript:
CommonJS modules are used in Node.js and designed to run on the server side. CommonJS modules use the
module.exports
andrequire()
keywords to export and import code.ECMAScript (ES) modules are a modern standard for JavaScript modules designed to run in the browser. ES modules use the
export
andimport
keywords to export and import code.
To create a module in JavaScript, you can define your code in a separate file and then use the export
keyword to make the code available to other parts of your application. For example, if you have a file called math.js
that contains a simple function to add two numbers, you can use the following code to export the function:
export function add(a, b) { return a + b; }
To use the module in another file, you can use the import
keyword to import the code. For example:
import { add } from './math'; console.log(add(1, 2)); // 3
Modules can also export variables, objects, and class definitions in addition to functions. You can also use the export default
syntax to specify a default export for your module.
Using modules can help you structure your code and make it easier to manage and maintain. It's a good practice to use modules whenever possible to make your code more modular and reusable.
Objects
Objects are a collection of properties. A property is an association between a name (or key) and a value. A property's value can be a function, in which case the property is known as a method.
In objects, we need to know three key topics:
- Prototypal Inheritance
this
keyword- Classes, Constructors, and
new
keyword
Prototypal Inheritance
All JS objects have a _proto_
property, a reference to another object, called object's prototype
. When a property is accessed on an object and if the property is not found on that object, the JS will go to the object's prototype
, then it will go to the prototype's prototype until it reaches the end of the prototype chain.
Prototype chain - chain going downwards, with null
at the top and the object in question at the bottom.
Useful case or why?
When invoking a function with new
, it sets the _proto_
reference to the prototype
of the constructing function.
// PROTOTYPE CHAIN function Fn() {} var obj = new Fn(); console.log(obj.__proto__ === Fn.prototype); // -> true console.log(obj.__proto__.__proto__ === Object.prototype); // -> true console.log(obj.__proto__.__proto__.__proto__ === null); // -> true
Why access the prototype instead of just initialising a method in the constructing function itself? Memory and performance. Accessing this
in a new object creates a new method.
If you need to create a thousand objects from the constructor function, using Fn.prototype.method
uses the method from Fn, for all the objects.
Array
, Object
and Function
all have inheritance methods we use every day. i.e. If we have const arr = []
, we can call arr.map()
because this method is present in Array.prototype
.
constructor = method to create and initialise an object in that class. Every function's prototype has a constructor
property that points back to the function itself.
Use case of constructor: logging the constructor property can show which function created that object.
Object.create
can be used to set the object's prototype manually.
More reference on prototypal inheritance.
this keyword
this
is a reference to the object that is executing the current function. It is a special keyword that is created for every execution context (every function). It is not assigned a value until a function where it is defined is called.
function Person(name, dob) { this.name = name; this.birthday = new Date(dob); this.calculateAge = () => { const diff = Date.now() - this.birtday.getTime(); const ageDate = new Date(diff); return Math.abs(ageDate.getUTCFullYear() - 1970); }; // Function call preceding `new` acts as a constructor. Inside the function, } // `this` will refer to a newly created `Object` const ilya = new Person('Ilya', '9-10-1981');
In the function execution context: this
is a reference to the current instance of the object.
Why? It always ensures that the correct values are used when a member's context changes, i.e. when you are dynamically generating objects.
- Rule 1: if calling
new
,this
inside the function is a brand new object - Rule 2: if calling
apply(array separated arguments)
,bind()
,call(comma separated arguments)
, orthis
inside the function equals to the parameter Object that is passed in.
function foo(a, b, c) {} var bar = {}; foo.apply(bar, [1, 2, 3]); // array will expand to the below foo.call(bar, 1, 2, 3); // results in a = 1, b = 2, c = 3 //
- Rule 3:
this
using dot notation is equal to the object to the left of that notation - Rule 4: if
this
is in the global scope, it pertains to the window object - If multiple rules apply, the rule that is higher wins
- If it is in an arrow function, it ignores all the rules above and receives the
this
value of its surrounding scope at the time it is created
*You can not assign a method to a variable
const test = someObject.methodTest; test(); // Test now acts like a plain action call, // `this` no longer refers to `someObject`
Read more on this keyword
Classes, Constructors, and new keyword
new
:
- creates a new object and binds it to the
this
of the constructor - sets the object's
prototype
,_proto_
to be theprototype
of the constructing function - If variables other than
object
,array
orfunction
is returned in the function, it always returnsthis
- returns
this
if there is no return in the function body
class PersonConstructor(name, age) { constructor(name, age) { this.name = name; this.age = age; } introduce() { clg(`Hi i am ${this.name}, I am ${this.age}`) } } class Boss extends PersonConstructor { constructor(name, age) { super (name, age) //takes us to the constructor of Person, this of Person = this of Boss } check() { clg(`Hooray i have a boss ${name}`) } } const ilya = new PersonConstructor('Ilya', 30) const boss1 = new Boss('Ilya', 29)
Constructor - any function call preceded by the new
keyword acts as a constructor. Every time we make a copy of the class
or constructing function, it passes the constructor function that gets called and creates preceding properties for that new function.
Asynchronous programming
Asynchronous programming is a programming paradigm that allows a program to perform multiple tasks concurrently rather than sequentially. This is particularly useful when dealing with tasks that may take a long time to complete, such as making network requests or reading and writing to a database.
In JavaScript, asynchronous programming is achieved using a combination of callback functions, Promises, and async/await.
Callbacks are functions that are passed as arguments to other functions and are executed when a certain event occurs, or a certain task is completed. For example, a callback function might be passed to a function that makes an HTTP request and would be executed when the response is received.
const createPost = (post, callback) => { posts.push(post); callback(); function getPosts() ... createPost(post, getPosts) // the second one is the callback }
Promises are a more advanced form of asynchronous programming in JavaScript. They represent a value that may be available in the future and allow you to attach callback functions to be executed when the value becomes available. Promises are helpful for chaining together asynchronous operations and can be used to handle errors more gracefully than with callback functions alone.
const createPost = () => { return new Promise((resolve, reject) => { setTimeout(() => { posts.push(post); if (!error) { resolve(); } else { reject('Error something wrong'); } }, 2000); }); }; function getPosts() { //... } createPost(postData) .then(getPosts) .catch((err) => { clg(err); });
Async/await is a syntax introduced in ECMAScript 2017 that makes it easier to write asynchronous code. It allows you to write code that looks like it is synchronous but is actually executed asynchronously. Async/await uses Promises under the hood but provides a more readable and intuitive syntax.
const getUsers = async () => { // Await response const response = await fetch('URLNAME'); // Wait till response is resolved const data = await response.json(); return data; };
Apply, Call, Bind
Bind()
creates a new function that, when called, has its this
set to the provided value, with a given sequence of arguments preceding any provided when the new function is called.
const pokemon = { firstName: 'Pika', lastName: 'Chu', getPokeName: () => { const fullName = this.firstName + '' + this.lastName; return fullName; } }; const pokemonName = () => { clg(this.getPokeName() + 'I chose you!'); }; const logPokemon = pokemonName.bind(pokemon); //creates new object and binds pokemon //`this` of pokemon === pokemon now logPokemon(); // Pika Chu I chose you!
Bind
is used in React class components to bind the value of this
in methods of classes you want to pass into other functions.
Apply()
- takes an Array of arguments
Call()
- takes Comma separated arguments
Both are used to call functions and the first parameter will be used as the value of this
within the function, followed by expecting the arguments
const pokemon = { firstName: 'Pika', lastName: 'Chu', getPokeName: () => { const fullName = this.firstName + '' + this.lastName; return fullName; } }; const pokemonName = (snack, hobby) => { clg(this.getPokeName) + snack + hobby; }; pokemonName.call(pokemon, 'sushi', 'algos'); // Pika Chu suchi algos
Main difference with bind()
:
- Accepts additional parameters
- Executes the function it was called upon right away
- Does not make a copy of the function it is being called on
Extra
For dessert, I have some additional topics I was never asked about but I think are essential to know:
- principles of browser
- Difference b-n == and ===
- *Polyfill Call(), apply(), bind(), map(), filter(), Array.reduce, Promise
- *Currying
- Context: where we are within an object
- Principle of least exposure = you should default to keeping everything private and only expose the minimum necessary
- IIFE - immediately invoked function expressions - putting a function in brackets to execute it immediately. It can be used any place when you need an expression
What questions have you found during your developer journey? Share the process of your journey with me. 🙇♂️
Recent publications
Found this article helpful? Try these as well ☺️