Questions for a Senior Software engineer to keep in mind for the next interview
I have structured this based on my recent experience as a Senior Javascript engineer with a few behavioral questions in the end which i thought were important.
1. Can you name two programming paradigms important for JavaScript app developers?
There are two main paradigms: imperative and declarative. In the declarative paradigm you are only concerned with the end result. You don’t care as much about the how you get the result (i.e: the recipe), that logic is abstracted away (e.g: a database query or a summation function). In imperative programming you are defining the procedure or the steps on how to get the result. I like this post as it has a more in depth description of paradigms. Here is an example:
Imperative
# Calculate total in the list
total = 0
myList = [1,2,3,4,5]
# Create a for loop to add numbers in the list to the total
for x in myList:
total += x
print(total)
Declarative
mylist = [1,2,3,4,5]
# set total to the sum of numbers in mylist
total = sum(mylist)
print(total)
JavaScript supports both. An example of imperative/procedural programming is OOP (Object-Oriented Programming) with prototypal inheritance. An example of declarative is functional programming which is more popular in modern javascript applications.
2. What is the difference between “classical” class inheritance and prototypal inheritance?
The most important difference is that a class defines a type which can be instantiated at runtime, whereas a prototype is itself an object instance.
Class Inheritance: instances inherit from classes (like a blueprint — a description of the class), and create sub-class relationships: hierarchical class taxonomies. Instances are typically instantiated via constructor functions with the `new` keyword. It is easy to misinterpret `class` keyword in JS as a class in a statically typed language. Instead, javascript has special functions that behave the way classes in statically typed languages like java.
Prototypal Inheritance: instances inherit directly from other objects. Instances are typically instantiated via factory functions or `Object.create()`. Instances may be composed from many different objects, allowing for easy selective inheritance. This is the preferred way to implement inheritance in javascript as its implementation aligns with the concept of functions as first-class objects.
There are 3 kinds of prototypal inheritance:
- Delegation (i.e., the prototype chain).
- Concatenative (i.e. mixins, `Object.assign()`).
- Functional (Not to be confused with functional programming. A function used to create a closure for private state/encapsulation).
Each type of prototypal inheritance has its own set of use-cases, but all of them are equally useful in their ability to enable composition, which creates has-a or uses-a or can-do relationships as opposed to the is-a relationship created with class inheritance.
3. What are javascript data types and how do they differ?
There are two groups of data types in javascript. Primitive and Non-Primitive.
Primitives (string/number/boolean/undefined/bigint) represent simple types and they are used to store a single value. They store data by value.
Non-Primitives (object/array/map/set) represent complex data types and they can hold multiple values. These store data by reference (i.e: we point to the same memory space when making a copy). This issue can avoided by using composition or spread operator which strips out the individual/primitive values. (See example below)
// This changes the original reference
const obj_a = {a : 1}
const obj_a_reference_copy = obj_a
obj_a_reference_copy['b'] = 1
// This does not change the original
obj_a = {a : 1}
obj_b = {b : 1}
obj_c = {...obj_a, ...obj_b, c : 1}
4. What is the difference between let, var and const in javascript?
A bit more context: Before ES6 (2015) there was only one way of defining your variables: with the var keyword. If you did not define them, they would be assigned to the global object. Unless you were in strict mode, you would get an error if your variables were undefined due to hoisting. Also scope was only defined within a block. Anything outside was considered global scope. ES6 Introduced function scope so var, let, const all take the scope of their lexical environment, e.g: local scope if defined within the function.
Now for the differences between each keyword:
- var has a function scope and can be redeclared within the same scope, whereas let and const have a block scope and can not be redeclared.
- var allows hoisting (i.e pull the declaration at the top of the function) where let and const does not.
- const is immutable whereas let and var can be changed. Once you bind a value to a variable using const, you can’t reassign a new value to that variable.
5. What is the difference between == and === in javascript?
Double/Shallow Equals ( ==) checks for value equality only. It inherently does type coercion. This means that before checking the values, it converts the types of the variables to match each other. Tripe equals does not do any type coercion and only considers value equality after the data types match.
e.g: (“1” == 1 => true whereas “1” === 1 => false)
6. Difference between callback and promise?
- A callback is simply a function passed as an argument to another function. This is how synchronous javascript works at its core. When you call the function, you also give it a success and an error callback/handler so that based on the response you can appropriately invoke the right function.
- A promise on the other hand is an object that may produce a single value some time in the future: either a resolved value, or a reason why it’s not resolved (e.g., a network error occurred). A promise may be in one of 3 possible states: fulfilled, rejected, or pending. Promise users can attach callbacks to handle the fulfilled value or the reason for rejection. Another promise definition: an object which can be returned synchronously from an asynchronous function.
7. What is Async/Await?
In short it is basically sugar syntax for promises. The await keyword causes the JavaScript runtime to pause your code on this line, allowing subsequent code to execute in the meantime, until the async function call has returned its result. Once that’s complete, your code continues to execute starting on the next line. Javascript engine handles this is in a very efficient way. I wrote a piece about this specifically: async explained.
8. What are the different data types in Javascript?
There are two main classifications. primitive and non-primitive. To know the type of a JavaScript variable, we can use the typeof operator.
a. Primitive types
String — It represents a series of characters and is written with quotes. A string can be represented using a single or a double quote.
var str = "Jim Smith"; // using double quotes
var str2 = 'John Doe'; // using single quotes
- Number — It represents a number (integer, floating point) and can be written with or without decimals.
var x = 3; // without decimal
var y = 3.6; // with decimal
- BigInt — This data type is used to store numbers which are above the limitation of the Number data type. It can store large integers and is represented by adding “n” to an integer literal.
var bigInteger = 234567890123456789012345678901234567890;
- Boolean — It represents a logical entity and can have only two values : true or false. Booleans are generally used for conditional testing.
var a = 2;
var b = 3;
var c = 2;
(a == b) // returns false
(a == c) // returns true
- Undefined — When a variable is declared but not assigned, it has the value of undefined and it’s type is also undefined.
var x; // value of x is undefined
var y = undefined; // we can also set the value of a variable as undefined
- Null — It represents a non-existent or a invalid value.
var z = null;
- Symbol — It is a new data type introduced in the ES6 version of javascript. It is used to store an anonymous and unique value.
var symbol1 = Symbol('symbol');
- typeof of primitive types :
typeof "John Doe" // Returns "string"
typeof 3.14 // Returns "number"
typeof true // Returns "boolean"
typeof 234567890123456789012345678901234567890n // Returns bigint
typeof undefined // Returns "undefined"
typeof null // Returns "object" (kind of a bug in JavaScript)
typeof Symbol('symbol') // Returns Symbol
b. Non-primitive types
- Primitive data types can store only a single value. To store multiple and complex values, non-primitive data types are used.
- Object — Used to store collection of data
- Array — Used to store a collection of data of similar type ( typically in a sequential memory order)
// collection of key value pairs
var obj1 = {
x: 43,
y: "Hello world!",
z: function(){
return this.x;
}
}
// Collection of data as an ordered listvar array1 = [5, 7, 9, 23];
9. What is hoisting in javascript?
Hoisting is the default behavior of javascript where all the variable and function declarations are moved up in the execution chain. Hosting respects the scope so for example a variable defined inside a function will be hoisted at the top of the function body, while a variable defined outside local scope will be hoisted at the top of the file.
hoistedVariable = 3;
console.log(hoistedVariable); // outputs 3 even when the variable is declared after it is initialized
var hoistedVariable; // gets moved to line 1, before the assignment
You can opt out of hoisting by using “strict” mode as in this example:
"use strict";
x = 23; // Gives an error since 'x' is not declared
var x;
Lets Talk Node
10. What is an error-first callback?
Most asynchronous methods exposed by the Node.js core API follow an idiomatic pattern referred to as an error-first callback. With this pattern, a callback function is passed to the method as an argument. When the operation either completes or an error is raised, the callback function is called with the Error
object (if any) passed as the first argument. If no error was raised, the first argument will be passed as null
.
11. What is the DOM and can you access it in node?
- DOM stands for Document Object Model. It is a programming interface for HTML and XML documents.
- When the browser tries to render an HTML document, it creates an object based on the HTML document called DOM
- Since node is a server side language it does not know about state changes in th client.
12. What is the distinction between client-side and server-side JavaScript?
Client-side JavaScript is made up of two parts, a fundamental language and predefined objects for performing JavaScript in a browser. JavaScript for the client is automatically included in the HTML pages. At runtime, the browser understands this script.
Server-side JavaScript, involves the execution of JavaScript code on a server in response to client requests. It handles these requests and delivers the relevant response to the client, which may include client-side JavaScript for subsequent execution within the browser.
13. How do you import the contents from another JavaScript file?
You can import another file using the require
keyword. Now with ES6 you can also use the static import
statement which is used to import read only live bindings which are exported by another module. Imported modules are in strict mode
whether you declare them as such or not. The import
statement cannot be used in embedded scripts unless such script has a type="module"
. Bindings imported are called live bindings because they are updated by the module that exported the binding. There is also a function-like dynamic import()
, which does not require scripts of type="module"
.
14. What is ECMAScript and what are the new concepts in ES6?
ECMAScript was created to standardize JavaScript, and ES6 is the 6th version of ECMAScript, it was published in 2015, and is also known as ECMAScript 2015. It introduced these new concepts:
- Classes: A class is a type of function, but instead of using the keyword function to initiate it, we use the keyword class, and the properties are assigned inside a constructor() method.
- Arrow Functions: Arrow functions allow us to write shorter function syntax, for example
const someFunc = () => {...}
Unlike functions, arrows share the same lexical this as their surrounding code. SO hopefully less confusion about what ‘this’ is.
15. Difference between put and patch?
Put is typically used update the whole record and patch is typically used for certain attributes.
16. Difference between http and https?
In https protocol data sent over the network is encrypted via SLL/TLS
17. What is componentDidMount in react?
Executed on the client side only after the first render.
18. What is the difference between state and props?
The state is a data structure that starts with a default value when a Component mounts. It may be mutated across time Props (short for properties) are a Component’s configuration. They are received from above and immutable as far as the Component receiving them is concerned.
19. When do you use a functional component vs a class component?
If your component has state or a lifecycle method(s), use a Class component. Otherwise, use a Functional component.
20. What is Redux?
Redux is a single store state management tool. It is immutable in a sense that a new state gets created every time there is an update. Besides state, there are two other core concepts that make the updates possible: Actions (which describe the event/update) and Reducers (which calculate the next state based on previous). It is very useful in React applications because you can rely on the store changes to update UI components across your app, however be mindful that it is different from your component state.
21. What are Hooks and describe a custom hook that you used
Hooks are a new feature added in React v16.8. It allows to use all React features without writing class components. For example, before version 16.8, we need a class component to manage state of a component. Now we can keep state in a functional component using useState
hook. I created a useDataFetcher hook which combines useReducer, useState, useEffect in order to fetch and track data loading at different states of the loading process. It can be invoked as many times as needed with different endpoints in order to abstract way the task of loading third party data to your UI component. The reducer provides the capability for this hook to share state across the application. Finally it keeps track of cancelled network requests (abort controller) and it handles the cleanup correctly.
22. Why were they introduced?
Deep breath….. OK lets go,
One reason to introduce hooks was the complexity in dealing with this
keyword inside class components. If not handled properly, this
will take some other value. That will result in breaking lines like this.setState()
and other event handlers. Using hooks, we avoid that complexity when working with functional components.
Class components do not minify very well and also make hot reloading unreliable. That is another inspiration to bring hooks.
Another reason is that, there is no specific way to reuse stateful component logic. Even though HOC and render props patterns address this problem, that asks for modifying the class component code. Hooks allow to share stateful logic without changing the component hierarchy.
Fourth reason is, in a complex class component, related code are scattered in different lifecycle methods. Example, in case of a data fetching, we do that mainly in componentDidMount()
and componentDidUpdate()
. Another example is, in case of event listeners, we use componentDidMount()
to bind an event and componentWillUnmount()
to unbind. Hooks instead helps to place related code together.
23. What is the useEffect
hook and name a couple of benefits
It is a way to manage data fetching, subscriptions and side effects in a React component. It helps us to avoid redundant code in different lifecycle methods of a class component. It can combine complicated logic into an isolated logical block.
24. What is Express, describe some benefits
Express is a light weight popular framework build on top of Node.JS to facilitate building REST APIs with NodeJS. It also provides some useful abstractions to easily tap in to must have functionality like authentication, logging, CSRF and data parsing.
25. What are the types of memory leaks in NodeJS?
- Global Resources (They release memory only when the server dies or is interrupted so if we use them careless they can fill up quickly and crash the server. Solution: consider moving the declaration to localized scope or if needed throughout the application to a database like Redis)
- Closures (They can hijack memory but holding on the references outside the scope. Solution: release the memory at the end of the scope block by setting resource to null)
- Promises (Promises by default refer to a future outcome (resolve/reject) but there is no guarantee they will ever complete thus polluting the memory space. Solution: Have a maximum time after which promises either resolve or get rejected. e.g: Promise.race)
- Caching (Similar to global resources if not used carefully it can flood the memory space. Solution: Periodically clean the cache)
26. What is npm and what are some commonly used What is npm-shrinkwrap and its relationship with package.json?
- Node package manager is a software register. It comes with standard out of the box tools to manage external libraries and their dependencies
- It’s most used script is npm install which creates a package.json file which lists all the external libraries, a package-lock.json which captures the exact version of the libraries themselves and their dependencies
- npm shrinkwrap is a script which can be used after installing npm-shrinkwrap which locks the dependency versions of your packages to ensure consistency across different development environments. This command repurposes
package-lock.json
into a publishablenpm-shrinkwrap.json
or simply creates a new one. The file created and updated by this command will then take precedence over any other existing or futurepackage-lock.json
files.
27. What are typescript utilities?
They are built-in types that help with type manipulation, example include:
- Partial => To make the code optional
- Required => To change all the properties to required
- Record => On object creation shortcut for defining the object’s key and value type
- Omit => Remove the key/s from the object type definition
- ReturnType => Extracts the return type of a function type, e.g:
type PointGenerator = () => { x: number; y: number; };
const point: ReturnType<PointGenerator> = {
x: 10,
y: 20
};
SYSTEM DESIGN QUESTIONS
28. What are the pros and cons of monolithic vs micro-service architectures?
A monolithic architecture means that your app is written as one cohesive unit of code whose components are designed to work together, sharing the same memory space and resources.
A micro-service architecture means that your app is made up of lots of smaller, independent applications capable of running in their own memory space and scaling independently from each other across potentially many separate machines.
Monolithic Pros: The major advantage of the monolithic architecture is that most apps typically have a large number of cross-cutting concerns, such as logging, rate limiting, and security features such audit trails and DOS protection.
Monolithic cons: Monolithic app services tend to get tightly coupled and entangled as the application evolves, making it difficult to isolate services for purposes such as independent scaling or code maintainability.
Microservice pros: Micro-service architectures are typically better organized, since each micro-service has a very specific job, and is not concerned with the jobs of other components. Decoupled services are also easier to recompose and reconfigure to serve the purposes of different apps (for example, serving both the web clients and public API).
They can be development in parallel due ti their independent nature which can be useful for large teams and tight deadlines.
Micro-service cons: As you’re building a new microservice architecture, you’re likely to discover lots of cross-cutting concerns that you did not anticipate at design time. A monolithic app could establish shared magic helpers or middleware to handle such cross-cutting concerns without much effort.
29. What are software design patterns and why are they useful
In software engineering, a software design pattern is a general, reusable solution to a commonly occurring problem within a given context in software design. Design Patterns are categorized mainly into three categories: Creational Design Pattern, Structural Design Pattern, and Behavioral Design Pattern. These are some common ones from the first category (more info)
- Singleton Design Pattern (Restricts the instantiation of a class and ensures that only one instance of the class exists. The singleton class must provide a global access point to get the instance of the class. Singleton pattern is used for logging, drivers objects, caching, and thread pool)
- Factory Method Design Pattern (It is a creational pattern that helps create an object without the user getting exposed to creational logic)
- Observer Design Pattern (The observer design pattern enables a subscriber to register with and receive notifications from a provider. It’s suitable for any scenario that requires push-based notification. The pattern defines a provider (also known as a subject or an observable) and zero, one, or more observers)
30. What is CAP Theorem, And how is MongoDB classified?
- In the context of the CAP Theorem MongoDB, MongoDB is often classified as an AP (Availability/Partition tolerance) database.
Trick JS Coding Question
setTimeout(() => console.log(1), 0);
console.log(2);
new Promise(res => {
console.log(3) res();
}).then(() => console.log(4));
console.log(5);
In which order will the numbers be printed out?
- Statement 1 vs 2: Even though the delay on the timeout is 0, it is still executed as an async operation (i.e: js engines adds it to callback queue/ macrotask queue) so statement
2
prints first - Next notice that the
Promise
constructor takes a function as an argument which is executed synchronously. Therefore, the next number to be displayed in the console is3
. - Next the callback in the
then
method is executed asynchronously, even though thepromise
resolves without delay. so5
prints first. The difference withsetTimeout
is that the engine will place thepromise
callback in another queue — the job queue (microtask queue), where it will wait for its turn to be executed. - Microtasks
promises
have a higher priority than macrotaskssetTimeout
, so the next number in the console will be4
and then1
.
31. How do you scale a system sustainably?
- Planning: Start with a clear understanding of current and future needs. Identify potential scalability bottlenecks, such as database performance, network bandwidth, or server capacity. Once you are clear start thinking about using modular architectures, microservices, and distributed systems where appropriate.
- Vertical and Horizontal Scaling: Think about how you want to scale your system. Vertical scaling (upgrading hardware resources such as CPU, RAM, and storage to handle increased loads, think SQL databases) and Horizontal scaling (adding more instances of servers or services to distribute the workload and increase capacity, think NoSQL databases).
- Load Balancing: Use load balancers to distribute incoming traffic across multiple servers or instances to prevent overloading any single component. Implement intelligent load balancing algorithms to dynamically adjust traffic distribution based on server health and capacity. You can go with a managed solution such as AWS Elastic Load Balancing .
- Caching: Utilize caching mechanisms to reduce the load on backend systems and improve response times. Implement caching at various layers (e.g., database caching, CDN caching, in-memory caching) to store frequently accessed data and static content.
- Monitoring and Optimization: Implement comprehensive monitoring and logging systems to track system performance, resource utilization, and potential bottlenecks. (AWS Cloudwatch or Azure Dynatrace).
- Cost Optimization: Continuously evaluate resource usage and costs to optimize infrastructure spending. For infrequently used applications/systems use a pay as you go model. Use cost management tools and services to monitor expenses and identify opportunities for cost savings, such as reserved instances, spot instances, or serverless computing.
BEHAVIORAL QUESTIONS
32. How do you learn best?
- I learn best via a combination of hands-on practice, iteration and collaboration with other engineers. For complex tasks i find it is best to break it down into smaller manageable subproblems and bring them altogether in the end.
- I also try to keep up with the latest innovations in frameworks i am currently working with or have some interest in. This could be an actual online course or different tutorials/podcasts that have a proven track record of accuracy.
33. What are you looking for in the next role?
- I want to continue on my career path to becoming a staff level engineer in the next two years. I plan to achieve this by collaborating at a high level with more experienced engineers while expanding my knowledge on designing for scale and performance. This is tough challenge but my experience working with distributed systems and with the team support i will be able to succeed on this journey.
34. Describe a time when you had a conflict and how did you resolve it?
- In my previous role as a senior software engineer, I encountered a conflict with a team member during a code review regarding one of the coding standards we had set for local development. The difference in opinion was wether we should make this standard (a pre-commit hook that lints the code) mandatory or optional. I was of the camp that this standard should be mandatory to avoid pushing inconsistent commits to the shared repository.
- To address the conflict, I proposed we make this a discussion point during our next engineering meeting. We agreed and during that meeting everyone was able to contribute their ideas. We compiled a list of pros and cons and then took a vote on a couple of popular ones.
- The final decision supported the mandatory option with the exception that you can opt out under special circumstances as long as you do the tasks mentioned on the hook manually.
- As a result, the team member felt heard and supported, and we both discovered a new way that satisfied both of our requirements. Reflecting on this experience, I learned the importance of listening and being opened to new ideas. This opened the door to resolving many more difficult decisions by taking in the input of a group that would be much harder to resolve between two people alone.