Now that you have prepared a quality JD, it can still be tricky to evaluate the skills of your applicants when you hire JavaScript developers. To help you with that, we have created a pool of questions that a good JavaScript developer should be comfortable with.
Note that the ability to answer these questions doesn't imply that you have a top quality candidate. But, it definitely is a big step in that direction.
To help you navigate through these questions, we’ve categorized the interview questions in 3 parts:
A. Basic concepts: Includes all basic concepts used across languages. This will give you an understanding of how strong their programming foundation is.
B. Advanced concepts: Includes all concepts that someone with higher expertise should know.
C. DS/Algorithm questions: To test the logical capability of the candidate.
A. Basic concepts
1. Given a string containing just the characters '(', ')', '{', '}', '[' and ']', determine if the input string is valid. An input string is valid if: Open brackets must be closed by the same type of brackets. Open brackets must be closed in the correct order. Note that an empty string is also considered valid.
class ValidParenthesesFunc {
func isValid(_ s: String) -> Bool {
var sta = [Character]()
for char in s {
if char == "(" || char == "[" || char == "{" {
sta.append(char)
} else if char == ")" {
guard sta.count != 0 && sta.removeLast() == "(" else {
return false
}
} else if char == "]" {
guard sta.count != 0 && sta.removeLast() == "[" else {
return false
}
} else if char == "}" {
guard sta.count != 0 && sta.removeLast() == "{" else {
return false
}
}
}
return sta.isEmpty
}
}
The above code will input 0(false).
What is the popular open standard file format (like XML) used with javascript and node.js? And what are the popular commands to convert string to that and vice versa?
It is JSON which stands for Javascript Object Notation. It is a lightweight data interchange format. It is easy for humans to read. Even though it has Javascript in its full form, it is language independent.
It’s built on 2 structures:
- Collection of key-value pairs. This is called an object, dictionary, hashmap, associative array, in different languages
- Ordered list of values, This is called an array, list, vector, in different languages
These are universal data structures which is important for a file format which is language independent.
Example of JSON:
{
"flexiple": {
"top": "1%",
"location": "remote"
},
"blog": {
"topic": "engineering",
"title": "What are stable coins and how do they work?"
}
}
This in XML would look like this:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<blog>
<title>What are stable coins and how do they work?</title>
<topic>engineering</topic>
</blog>
<flexiple>
<location>"remote"</location>
<top>1%</top>
</flexiple>
</root>
Valid JSON
Few of the rules to remember while handling JSON are that:
- Empty objects and arrays are okay
- Strings can contain any unicode character, this includes object properties
- null is a valid JSON value on its own
- All object properties should always be double quoted
- Object property values must be one of the following: String, Number, Boolean, Object, Array, null
- Number values must be in decimal format, no octal or hex representations
- Trailing commas on arrays are not allowed
These are all examples of valid JSON: -
{"name":"John Doe","age":32,"title":"Vice President of JavaScript"}
["one", "two", "three"]
// nesting valid values is okay
{"names": ["John Doe", "Jane Doe"] }
[ { "name": "John Doe"}, {"name": "Jane Doe"} ]
{} // empty hash
[] // empty list
null
{ "key": "\uFDD0" } // unicode escape codes
These are all examples of bad JSON formatting:-
{ name: "John Doe", 'age': 32 } // name and age should be in double quotes
[32, 64, 128, 0xFFF] // hex numbers are not allowed
{ "name": "John Doe", "age": undefined } // undefined is an invalid value
// functions and dates are not allowed
{ "name": "John Doe",
"birthday": new Date('Fri, 26 Jan 2019 07:13:10 GMT'),
"getName": function() {
return this.name;
}
}
Javascript provides 2 methods to encode data structures to JSON and to convert JSON to objects or arrays. These methods are JSON.stringify() and JSON.parse().
JSON.stringify()
It takes a Javascript object or array as input and returns a serialised string form of it.
const obj = {
name: "Flexiple",
city: "Bengaluru"
}
const jsonStr = JSON.stringify(obj)
console.log(jsonStr)
// output is {"name":"Flexiple","city":"Bengaluru"}
JSON.stringify() can take 3 arguments in total, the first one being the data structure.
The second argument, called the replacer parameter, can either be a function or an array. It is used to manipulate the properties (keys and values) in JSON.
let cost = {
candy: 5,
bread: 20,
cheese: 100,
milk: 15
}
let func = (key, value) => {
if (value < 15) {
return undefined
}
return value
}
let arr = ['candy', 'bread']
let jsonStrWithFunc = JSON.stringify(cost, func)
console.log(jsonStrWithFunc)
// Output is {"bread":20,"cheese":100,"milk":15}
let jsonStrWithArr = JSON.stringify(cost, arr)
console.log(jsonStrWithArr)
// Output is {"candy":5,"bread":20}
The third argument, called the space parameter, takes in either a number or a string. It is used to control the spacing of the final string by deciding the size of indentation.
let cost = {
candy: 5,
bread: 20,
cheese: 100,
milk: 15
}
let func = (key, value) => {
if (value < 15) {
return undefined
}
return value
}
let arr = ['candy', 'bread']
let jsonStrWithFunc = JSON.stringify(cost, func)
console.log(jsonStrWithFunc)
// Output is {"bread":20,"cheese":100,"milk":15}
let jsonStrWithArr = JSON.stringify(cost, arr)
console.log(jsonStrWithArr)
// Output is {"candy":5,"bread":20}
JSON.stringify() is also popularly used for debugging, as trying to see a JSON object using console.log() isn’t always successful
const obj = {
"nest1": {
"ex": "ex",
"nest2": {
"nest3": {
"ex": "ex",
}
}
}
}
console.log(obj)
// Output is { nest1: { ex: 'ex', nest2: { nest3: [Object] } } }
As we can see that after a certain level of nesting the log just displays ‘[Object]’. Therefore, JSON.stringify() can be used to make debugging easier.
const obj = {
"nest1": {
"ex": "ex",
"nest2": {
"nest3": {
"ex": "ex",
}
}
}
}
console.log(JSON.stringify(obj,null,2))
// Output is
// {
// "nest1": {
// "ex": "ex",
// "nest2": {
// "nest3": {
// "ex": "ex"
// }
// }
// }
// }
JSON.parse()
It takes a JSON string and converts it into its respective data structure
const jsonStr = '{"name":"Flexiple","city":"Bengaluru"}'
const obj = JSON.parse(jsonStr)
console.log(obj)
// output is { name: 'Flexiple', city: 'Bengaluru' }
Calling JSON.parse() on invalid JSON string will throw a SyntaxError.
Cloning Objects in Javascript
These 2 functions of JSON can be used together to clone javascript objects.
const obj = {
"prop1": {
"ex": "ex",
"prop2": {
"ex": "ex"
}
}
}
const objCopy = JSON.parse(JSON.stringify(obj))
console.log(objCopy)
// Output is { prop1: { ex: 'ex', prop2: { ex: 'ex' } } }
But this is not recommended, because of multiple reasons:
- It is slower than alternatives
- This works properly only for valid JSON, but not all javascript objects fall in that category
A better alternative for deep cloning objects would be to either use an npm library called ‘lodash’ or write a custom recursive function to do the same. The spread operator can be used for shallow cloning of objects.
B. Advanced concepts
What is a lint? Have you ever worked with one before, if yes which one?
Linting is the automatic checking of source code for programmatic and stylistic errors. Programmatic errors are the errors such as compilation and run time errors. Each company decides to write their code in a certain style and format, this is done for easy maintenance and to provide further structure to the code.
Stylistic errors are those which might compile without an issue but it does not meet the standards set by the company. Apart from stylistic checks, it is able to identify certain types of bugs like those related to variable scope, undeclared variables, global variables and so on.
Linting is done using a lint tool or linter, which is basically a static code analyzer. It is important as it reduces errors and increases the standard of the code. It can greatly benefit early stages of development and can save time during code reviews and code maintenance.
ESLint
ESLint is one the most popular lints available for Javascript. Let us go into the basics of using ESLint. This is going to the most basic version of ESLint, remember you can install it with different code style plugins as per your choice.
- After setting up your project (that is running ‘npm init’), run ‘npm install eslint --save-dev’.
- After that we run ‘npx eslint --init’ this is used to generate the ‘.eslintrc.json’ file which is the config file for eslint.
- When we are generating a the config file, it asks a bunch of questions to generate it, these are what I’ve chosen:
√ How would you like to use ESLint? · style
√ What type of modules does your project use? · commonjs
√ Which framework does your project use? · none
√ Does your project use TypeScript? · No / Yes
√ Where does your code run? · node
√ How would you like to define a style for your project? · guide
√ Which style guide do you want to follow? · standard
√ What format do you want your config file to be in? · JSON
- It might have some peer dependencies that it wants you to install - install them too.
- Now we come to using the linter. We run ‘npx eslint app.js’ (here app.js is the source js file).
My app.js file looks like this:
const obj = {
"prop1": {
"ex": "ex",
"prop2": {
"ex": "ex"
}
}
}
const objCopy = JSON.parse(JSON.stringify(obj))
console.log(objCopy)
And the output after running ‘npx eslint app.js’ is:
2:1 error Expected indentation of 2 spaces but found 4 indent
2:5 error Strings must use singlequote quotes
2:5 error Unnecessarily quoted property 'prop1' found quote-props
3:1 error Expected indentation of 4 spaces but found 8 indent
3:9 error Unnecessarily quoted property 'ex' found quote-props
3:9 error Strings must use singlequote quotes
3:15 error Strings must use singlequote quotes
4:1 error Expected indentation of 4 spaces but found 8 indent
4:9 error Strings must use singlequote quotes
4:9 error Unnecessarily quoted property 'prop2' found quote-props
5:1 error Expected indentation of 6 spaces but found 12 indent
5:13 error Strings must use singlequote quotes
5:13 error Unnecessarily quoted property 'ex' found quote-props
5:19 error Strings must use singlequote quotes
6:1 error Expected indentation of 4 spaces but found 8 indent
7:1 error Expected indentation of 2 spaces but found 4 indent
11:1 error Trailing spaces not allowed no-trailing-spaces
12:21 error Newline required at end of file but not found eol-last
✖ 18 problems (18 errors, 0 warnings)
18 errors and 0 warnings potentially fixable with the `--fix` option.
Some of the errors can be fixed using the ‘--fix’ flag, so run ‘npx eslint app.js --fix’. This is how my app.js changed after that:
const obj = {
prop1: {
ex: 'ex',
prop2: {
ex: 'ex'
}
}
}
const objCopy = JSON.parse(JSON.stringify(obj))
console.log(objCopy)
Now it does not show any errors on running ‘npx eslint app.js’.
Similarly ESLint can be used to either check syntax, fix bugs, enforce code style and it has a lot of different options to choose from. It supports plugins and allows custom rules also. Apart from ESLint, some of the other popular lints for Javascript are JSLint and JSHint.
What are the vulnerabilities of using cookies? What about the usage of local storage?
Cookies and local storage are primarily used for session management in web application development. What this means is that once the user is authenticated, there needs to be a way for the application to remember the user for a period of time without asking the user to login again.
In an architecture design it is standard to keep the server stateless, but this would mean that the user information cannot be stored on the server. Therefore, the decision was taken to store it on the client. This was originally done using cookies. Cookies are basically a storage facility with the browser, which the client side javascript or the server (using headers) can interact with.
Each item stored in cookies is called a cookie, and each cookie has an expiration time which can be manually set too. The cookie persists in the browser for that duration and is not removed by page refreshes or window being shut. When a client interacts with a server using HTTP requests, then the cookies are also sent along in headers.
The data which is stored in a cookie is generally a token such as a JSON Web Token (JWT). JWT consists of a payload which is used to fetch information about the user. JWT tokens are signed on the server using a secret key. The routes allow only authorized users to check whether the token is present in the cookies. This is the outline of the architecture followed for session management.
Difference between Cookie and Local Storage
Primarily both function in similar ways - i.e. both involve persistent storage in the browser. The differences come in slight nuances of their functioning:
- Local storage does not have the concept of expiration time. So, the developer dealing with it needs to handle the expiration of tokens stored in it. Whereas, the expiration time for cookies can be set while storing the cookie and the browser handles the rest.
- Local storage is not sent with every HTTP request, like a cookie is sent. This reduces load on especially those HTTP requests which are public and do not require to use the token stored.
- The server can directly interact with cookies, whereas only the client side script can interact with local storage.
- Local storage has a much larger storage capacity of 5mb compared to 4kb of cookie. This is primarily because cookies are meant to be read by the server, whereas local storage is meant for the client side to be able to persistently store data.
Vulnerabilities
Local storage
Local storage is accessible only by client-side javascript code. This makes it particularly vulnerable to XSS attacks. XSS (Cross-Site Scripting) attacks are a type of injections in which malicious scripts are injected into otherwise trusted websites due to a vulnerability.
This script then executes on the client’s browser and can access the local storage quite easily. XSS attacks are mainly used to steal cookies, session tokens and other information. After obtaining session tokens they can access protected routes using it. Therefore, storing authentication tokens on local storage is very risky.
Cookies
Cookies function similarly as they can also be accessed by client-side javascript code. They are also vulnerable to XSS attacks, but there can be a further layer of protection added to prevent this.
If the ‘httpOnly’ flag is marked as true while setting cookies then client side javascript cannot access that cookie. This allows only the server side code to interact with it. So this protects it from XSS attacks. However, due to its property of sending a cookie on every request, it gets vulnerable to CSRF/XSRF attacks.
CSRF/XSRF (Cross-Site Request Forgery) attacks are when malicious web apps can influence the interaction between a client browser and a web server that trusts the browser. Therefore, further measures need to be taken like using CSRF tokens and proper use of the Same-Site cookie attribute.
C. Data Structure/ Algorithm
1. Given a string containing just the characters '(', ')', '{', '}', '[' and ']', determine if the input string is valid. An input string is valid if: Open brackets must be closed by the same type of brackets. Open brackets must be closed in the correct order. Note that an empty string is also considered valid.
class ValidParenthesesF {
func isValid(_ s: String) -> Bool {
var stg = [Character]()
for char in s {
if char == "(" || char == "[" || char == "{" {
stg.append(char)
} else if char == ")" {
guard stg.count != 0 && stg.removeLast() == "(" else {
return false
}
} else if char == "]" {
guard stg.count != 0 && stg.removeLast() == "[" else {
return false
}
} else if char == "}" {
guard stg.count != 0 && stg.removeLast() == "{" else {
return false
}
}
}
return stg.isEmpty
}
}
The above code will input 0(false).
2. What will the output of the following code be?
var p = 2;
var q = 4;
var r = 6;
if (p > q > r)
document.write("true");
else
document.write("false");
The answer is False. It may look like the output can be true because 6 > 4 > 2 is true, but PHP evaluates $z > $y
first, which returns a boolean value of 1 or true. This value (true or 1) is compared to the
next integer in the chain, bool(1) > $z, which will result in NULL and echo “false.”