Data Structures Interview
Last updated
Last updated
Asymptotic Notation is the hardware independent notation used to tell the time and space complexity of an algorithm. Meaning it's a standardized way of measuring how much memory an algorithm uses or how long it runs for given an input.
The following are the Asymptotic rates of growth from best to worst:
constant growth - O(1)
Runtime is constant and does not grow with n
logarithmic growth – O(log n)
Runtime grows logarithmically in proportion to n
linear growth – O(n)
Runtime grows directly in proportion to n
superlinear growth – O(n log n)
Runtime grows in proportion and logarithmically to n
polynomial growth – O(n^c)
Runtime grows quicker than previous all based on n
exponential growth – O(c^n)
Runtime grows even faster than polynomial growth based on n
factorial growth – O(n!)
Runtime grows the fastest and becomes quickly unusable for even small values of n
(source: Soumyadeep Debnath, Analysis of Algorithms | Big-O analysis) Visualized below; the x-axis representing input size and the y-axis representing complexity: (source: Wikipedia, Computational Complexity of Mathematical Operations)
Big-O refers to the upper bound of time or space complexity of an algorithm, meaning it worst case runtime scenario. An easy way to think of it is that runtime could be better than Big-O but it will never be worse.
Big-Omega refers to the lower bound of time or space complexity of an algorithm, meaning it is the best runtime scenario. Or runtime could worse than Big-Omega, but it will never be better.
Big-Theta refers to the tight bound of time or space complexity of an algorithm. Another way to think of it is the intersection of Big-O and Big-Omega, or more simply runtime is guaranteed to be a given complexity, such as n log n
.
Big-O and Big-Theta are the most common and helpful notations
Big-O does not mean Worst Case Scenario, Big-Theta does not mean average case, and Big-Omega does not mean Best Case Scenario. They only connote the algorithm's performance for a particular scenario, and all three can be used for any scenario.
Worst Case means given an unideal input, Average Case means given a typical input, Best case means a ideal input. Ex. Worst case means given an input the algorithm performs particularly bad, or best case an already sorted array for a sorting algorithm.
Best Case and Big Omega are generally not helpful since Best Cases are rare in the real world and lower bound might be very different than an upper bound.
Big-O isn't everything. On paper merge sort is faster than quick sort, but in practice quick sort is superior.
Stores data elements based on an sequential, most commonly 0 based, index.
Based on tuples from set theory.
They are one of the oldest, most commonly used data structures.
Optimal for indexing; bad at searching, inserting, and deleting (except at the end).
Linear arrays, or one dimensional arrays, are the most basic.
Are static in size, meaning that they are declared with a fixed size.
Dynamic arrays are like one dimensional arrays, but have reserved space for additional elements.
If a dynamic array is full, it copies its contents to a larger array.
Multi dimensional arrays nested arrays that allow for multiple dimensions such as an array of arrays providing a 2 dimensional spacial representation via x, y coordinates.
Indexing: Linear array: O(1)
, Dynamic array: O(1)
Search: Linear array: O(n)
, Dynamic array: O(n)
Optimized Search: Linear array: O(log n)
, Dynamic array: O(log n)
Insertion: Linear array: n/a, Dynamic array: O(n)
Stores data with nodes that point to other nodes.
Nodes, at its most basic it has one datum and one reference (another node).
A linked list chains nodes together by pointing one node's reference towards another node.
Designed to optimize insertion and deletion, slow at indexing and searching.
Doubly linked list has nodes that also reference the previous node.
Circularly linked list is simple linked list whose tail, the last node, references the head, the first node.
Stack, commonly implemented with linked lists but can be made from arrays too.
Stacks are last in, first out (LIFO) data structures.
Made with a linked list by having the head be the only place for insertion and removal.
Queues, too can be implemented with a linked list or an array.
Queues are a first in, first out (FIFO) data structure.
Made with a doubly linked list that only removes from head and adds to tail.
Indexing: Linked Lists: O(n)
Search: Linked Lists: O(n)
Optimized Search: Linked Lists: O(n)
Append: Linked Lists: O(1)
Prepend: Linked Lists: O(1)
Insertion: Linked Lists: O(n)
Stores data with key value pairs.
Hash functions accept a key and return an output unique only to that specific key.
This is known as hashing, which is the concept that an input and an output have a one-to-one correspondence to map information.
Hash functions return a unique address in memory for that data.
Designed to optimize searching, insertion, and deletion.
Hash collisions are when a hash function returns the same output for two distinct inputs.
All hash functions have this problem.
This is often accommodated for by having the hash tables be very large.
Hashes are important for associative arrays and database indexing.
Indexing: Hash Tables: O(1)
Search: Hash Tables: O(1)
Insertion: Hash Tables: O(1)
Is a tree like data structure where every node has at most two children.
There is one left and right child node.
Designed to optimize searching and sorting.
A degenerate tree is an unbalanced tree, which if entirely one-sided, is essentially a linked list.
They are comparably simple to implement than other data structures.
Used to make binary search trees.
A binary tree that uses comparable keys to assign which direction a child is.
Left child has a key smaller than its parent node.
Right child has a key greater than its parent node.
There can be no duplicate node.
Because of the above it is more likely to be used as a data structure than a binary tree.
Indexing: Binary Search Tree: O(log n)
Search: Binary Search Tree: O(log n)
Insertion: Binary Search Tree: O(log n)
An algorithm that calls itself in its definition.
Recursive case a conditional statement that is used to trigger the recursion.
Base case a conditional statement that is used to break the recursion.
Stack level too deep and stack overflow.
If you've seen either of these from a recursive algorithm, you messed up.
It means that your base case was never triggered because it was faulty or the problem was so massive you ran out of alloted memory.
Knowing whether or not you will reach a base case is integral to correctly using recursion.
Often used in Depth First Search
An algorithm that is called repeatedly but for a finite number of times, each time being a single iteration.
Often used to move incrementally through a data set.
Generally you will see iteration as loops, for, while, and until statements.
Think of iteration as moving one at a time through a set.
Often used to move through an array.
The differences between recursion and iteration can be confusing to distinguish since both can be used to implement the other. But know that,
Recursion is, usually, more expressive and easier to implement.
Iteration uses less memory.
Functional languages tend to use recursion. (i.e. Haskell)
Imperative languages tend to use iteration. (i.e. Ruby)
Check out this Stack Overflow post for more info.
An algorithm that, while executing, selects only the information that meets a certain criteria.
The general five components, taken from Wikipedia:
A candidate set, from which a solution is created.
A selection function, which chooses the best candidate to be added to the solution.
A feasibility function, that is used to determine if a candidate can be used to contribute to a solution.
An objective function, which assigns a value to a solution, or a partial solution.
A solution function, which will indicate when we have discovered a complete solution.
Used to find the expedient, though non-optimal, solution for a given problem.
Generally used on sets of data where only a small proportion of the information evaluated meets the desired result.
Often a greedy algorithm can help reduce the Big O of an algorithm.
This algorithm never needed to compare all the differences to one another, saving it an entire iteration.
An algorithm that searches a tree (or graph) by searching levels of the tree first, starting at the root.
It finds every node on the same level, most often moving left to right.
While doing this it tracks the children nodes of the nodes on the current level.
When finished examining a level it moves to the left most node on the next level.
The bottom-right most node is evaluated last (the node that is deepest and is farthest right of it's level).
Optimal for searching a tree that is wider than it is deep.
Uses a queue to store information about the tree while it traverses a tree.
Because it uses a queue it is more memory intensive than depth first search.
The queue uses more memory because it needs to stores pointers
Search: Breadth First Search: O(V + E)
E is number of edges
V is number of vertices
An algorithm that searches a tree (or graph) by searching depth of the tree first, starting at the root.
It traverses left down a tree until it cannot go further.
Once it reaches the end of a branch it traverses back up trying the right child of nodes on that branch, and if possible left from the right children.
When finished examining a branch it moves to the node right of the root then tries to go left on all it's children until it reaches the bottom.
The right most node is evaluated last (the node that is right of all it's ancestors).
Optimal for searching a tree that is deeper than it is wide.
Uses a stack to push nodes onto.
Because a stack is LIFO it does not need to keep track of the nodes pointers and is therefore less memory intensive than breadth first search.
Once it cannot go further left it begins evaluating the stack.
Search: Depth First Search: O(|E| + |V|)
E is number of edges
V is number of vertices
The simple answer to this question is that it depends on the size and shape of the tree.
For wide, shallow trees use Breadth First Search
For deep, narrow trees use Depth First Search
Because BFS uses queues to store information about the nodes and its children, it could use more memory than is available on your computer. (But you probably won't have to worry about this.)
If using a DFS on a tree that is very deep you might go unnecessarily deep in the search. See xkcd for more information.
Breadth First Search tends to be a looping algorithm.
Depth First Search tends to be a recursive algorithm.
A comparison based sorting algorithm.
Starts with the cursor on the left, iterating left to right
Compares the left side to the right, looking for the smallest known item
If the left is smaller than the item to the right it continues iterating
If the left is bigger than the item to the right, the item on the right becomes the known smallest number
Once it has checked all items, it moves the known smallest to the cursor and advances the cursor to the right and starts over
As the algorithm processes the data set, it builds a fully sorted left side of the data until the entire data set is sorted
Changes the array in place.
Inefficient for large data sets.
Very simple to implement.
Best Case Sort: Merge Sort: O(n^2)
Average Case Sort: Merge Sort: O(n^2)
Worst Case Sort: Merge Sort: O(n^2)
Worst Case: O(1)
A comparison based sorting algorithm.
Iterates left to right comparing the current cursor to the previous item.
If the cursor is smaller than the item on the left it swaps positions and the cursor compares itself again to the left hand side until it is put in its sorted position.
As the algorithm processes the data set, the left side becomes increasingly sorted until it is fully sorted.
Changes the array in place.
Inefficient for large data sets, but can be faster for than other algorithms for small ones.
Although it has an O(n^2)
, in practice it slightly less since its comparison scheme only requires checking place if its smaller than its neighbor.
Best Case: O(n)
Average Case: O(n^2)
Worst Case: O(n^2)
Worst Case: O(n)
A divide and conquer algorithm.
Recursively divides entire array by half into subsets until the subset is one, the base case.
Once the base case is reached results are returned and sorted ascending left to right.
Recursive calls are returned and the sorts double in size until the entire array is sorted.
This is one of the fundamental sorting algorithms.
Know that it divides all the data into as small possible sets then compares them.
Worst Case: O(n log n)
Average Case: O(n log n)
Best Case: O(n)
Worst Case: O(1)
A divide and conquer algorithm
Partitions entire data set in half by selecting a random pivot element and putting all smaller elements to the left of the element and larger ones to the right.
It repeats this process on the left side until it is comparing only two elements at which point the left side is sorted.
When the left side is finished sorting it performs the same operation on the right side.
Computer architecture favors the quicksort process.
Changes the array in place.
While it has the same Big O as (or worse in some cases) many other sorting algorithms it is often faster in practice than many other sorting algorithms, such as merge sort.
Worst Case: O(n^2)
Average Case: O(n log n)
Best Case: O(n log n)
Worst Case: O(log n)
Quicksort is likely faster in practice, but merge sort is faster on paper.
Merge Sort divides the set into the smallest possible groups immediately then reconstructs the incrementally as it sorts the groupings.
Quicksort continually partitions the data set by a pivot, until the set is recursively sorted.
Khan Academy's Algorithm Course
ARIA stands for "Accessible Rich Internet Applications", and is a technical specification created by the World Wide Web Consortium (W3C). Better known as WAI-ARIA, it provides additional HTML attributes in the development of web applications to offer people who use assistive technologies (AT) a more robust and interoperable experience with dynamic components. By providing the component's role, name, and state, AT users can better understand how to interact with the component. WAI-ARIA should only be used when an HTML element equivalent is not available or lacks full browser or AT support. WAI-ARIA's semantic markup coupled with JavaScript works to provide an understandable and interactive experience for people who use AT. An example using ARIA:
...Credit: W3C's [ARIA 1.1 Combobox with Grid Popup Example](https://w3c.github.io/aria-practices/examples/combobox/aria1.1pattern/grid-combo.html)
Accessible Rich Internet Applications
Benefits people who use assistive technologies (AT)
Provides role, name, and state
Semantic HTML coupled with JavaScript
4.5:1 is the calculated contrast ratio between foreground text and background that is recommended by the Web Content Accessibiity Guidelines (WCAG) success criteria (SC) 1.4.3: Contrast (Minimum). This SC was written by the World Wide Web Consortium (W3C) to ensure that people with low vision or color deficiencies are able to read (perceive) important content on a web page. It goes beyond color choices to ensure text stands out on the background it's placed on.
At least 4.5:1 contrast ratio between foreground text and background
Benefits people with low vision or color deficiencies
There are multiple tools that can help you to find for accessibility issues in your website or application. Check for issues in your website:
Lighthouse from Google, it provides an option for accessibility testing, it will check for the compliance of different accessibility standards and give you an score with details on the different issues
Axe Coconut from DequeLabs, it is a Chrome extension that adds a tab in the Developer tools, it will check for accessibility issues and it will classify them by severity and suggest possible solutions Check for issues in your code: * Jest Axe, you can add unit tests for accessibility * React Axe, test your React application with the axe-core accessibility testing library. Results will show in the Chrome DevTools console. * eslint-plugin-jsx-a11y, pairing this plugin with an editor lint plugin, you can bake accessibility standards into your application in real-time. Check for individual issues: * Color Contrast checkers * Use a screen reader * Use only keyboard to navigate your site
None of the tools will replace manual testing
Mention of different ways to test accessibility
The Accessibility Tree is a structure produced by the browser's Accessibility APIs which provides accessibility information to assistive technologies such as screen readers. It runs parallel to the DOM and is similar to the DOM API, but with much fewer nodes, because a lot of that information is only useful for visual presentation. By writing semantic HTML we can take advantage of this process in creating an accessible experience for our users.
Tree structure exposing information to assistive technologies
Runs parallel to the DOM
Semantic HTML is essential in creating accessible experiences
alt
attribute on images?The alt
attribute provides alternative information for an image if a user cannot view it. The alt
attribute should be used to describe any images except those which only serve a decorative purpose, in which case it should be left empty.
Decorative images should have an empty alt
attribute.
Web crawlers use alt
tags to understand image content, so they are considered important for Search Engine Optimization (SEO).
Put the .
at the end of alt
tag to improve accessibility.
defer
and async
attributes on a <script>
tag?If neither attribute is present, the script is downloaded and executed synchronously, and will halt parsing of the document until it has finished executing (default behavior). Scripts are downloaded and executed in the order they are encountered. The defer
attribute downloads the script while the document is still parsing but waits until the document has finished parsing before executing it, equivalent to executing inside a DOMContentLoaded
event listener. defer
scripts will execute in order. The async
attribute downloads the script during parsing the document but will pause the parser to execute the script before it has fully finished parsing. async
scripts will not necessarily execute in order. Note: both attributes must only be used if the script has a src
attribute (i.e. not an inline script).
Placing a defer
script in the <head>
allows the browser to download the script while the page is still parsing, and is therefore a better option than placing the script before the end of the body.
If the scripts rely on each other, use defer
.
If the script is independent, use async
.
Use defer
if the DOM must be ready and the contents are not placed within a DOMContentLoaded
listener.
async
function?An async
function is a function that allows you to pause the function's execution while it waits for (await
s) a promise to resolve. It's an abstraction on top of the Promise API that makes asynchronous operations look like they're synchronous. async
functions automatically return a Promise object. Whatever you return
from the async
function will be the promise's resolution. If instead you throw
from the body of an async
function, that will be how your async function rejects the promise it returns. Most importantly, async
functions are able to use the await
keyword in their function body, which pauses the function until the operation after the await
completes, and allows it to return that operation's result to a variable synchronously.
async
functions are just syntactic sugar on top of Promises.
They make asynchronous operations look like synchronous operations in your function.
They implicitly return a promise which resolves to whatever your async
function returns, and reject to whatever your async
function throw
s.
batches
that returns the maximum number of whole batches that can be cooked from a recipe.We must have all ingredients of the recipe available, and in quantities that are more than or equal to the number of units required. If just one of the ingredients is not available or lower than needed, we cannot make a single batch. Use Object.keys()
to return the ingredients of the recipe as an array, then use Array.prototype.map()
to map each ingredient to the ratio of available units to the amount required by the recipe. If one of the ingredients required by the recipe is not available at all, the ratio will evaluate to NaN
, so the logical OR operator can be used to fallback to 0
in this case. Use the spread ...
operator to feed the array of all the ingredient ratios into Math.min()
to determine the lowest ratio. Passing this entire result into Math.floor()
rounds down to return the maximum number of whole batches.
The BEM methodology is a naming convention for CSS classes in order to keep CSS more maintainable by defining namespaces to solve scoping issues. BEM stands for Block Element Modifier which is an explanation for its structure. A Block is a standalone component that is reusable across projects and acts as a "namespace" for sub components (Elements). Modifiers are used as flags when a Block or Element is in a certain state or is different in structure or style.
Here is an example with the class names on markup:
In this case, navbar
is the Block, navbar__link
is an Element that makes no sense outside of the navbar
component, and navbar__link--active
is a Modifier that indicates a different state for the navbar__link
Element. Since Modifiers are verbose, many opt to use is-*
flags instead as modifiers.
These must be chained to the Element and never alone however, or there will be scope issues.
Alternative solutions to scope issues like CSS-in-JS
Big O notation is used in Computer Science to describe the time complexity of an algorithm. The best algorithms will execute the fastest and have the simplest complexity. Algorithms don't always perform the same and may vary based on the data they are supplied. While in some cases they will execute quickly, in other cases they will execute slowly, even with the same number of elements to deal with. In these examples, the base time is 1 element = 1ms
.
1000 elements = 1ms
Constant time complexity. No matter how many elements the array has, it will theoretically take (excluding real-world variation) the same amount of time to execute.
1000 elements = 1000ms
Linear time complexity. The execution time will increase linearly with the number of elements the array has. If the array has 1000 elements and the function takes 1ms to execute, 7000 elements will take 7ms to execute. This is because the function must iterate through all elements of the array before returning a result.
1000 elements = 1ms <= x <= 1000ms
The execution time varies depending on the data supplied to the function, it may return very early or very late. The best case here is O(1) and the worst case is O(N).
1000 elements ~= 10000ms
Browsers usually implement the quicksort algorithm for the sort()
method and the average time complexity of quicksort is O(NlgN). This is very efficient for large collections.
1000 elements = 1000000ms
The execution time rises quadratically with the number of elements. Usually the result of nesting loops.
1000 elements = Infinity
(practically) ms The execution time rises extremely fast with even just 1 addition to the array.
Be wary of nesting loops as execution time increases exponentially.
bind
that is functionally equivalent to the method Function.prototype.bind
.Return a function that accepts an arbitrary number of arguments by gathering them with the rest ...
operator. From that function, return the result of calling the fn
with Function.prototype.apply
to apply the context and the array of arguments to the function.
Browsers have a cache to temporarily store files on websites so they don't need to be re-downloaded again when switching between pages or reloading the same page. The server is set up to send headers that tell the browser to store the file for a given amount of time. This greatly increases website speed and preserves bandwidth. However, it can cause problems when the website has been changed by developers because the user's cache still references old files. This can either leave them with old functionality or break a website if the cached CSS and JavaScript files are referencing elements that no longer exist, have moved or have been renamed. Cache busting is the process of forcing the browser to download the new files. This is done by naming the file something different to the old file. A common technique to force the browser to re-download the file is to append a query string to the end of the file.
src="js/script.js"
=> src="js/script.js?v=2"
The browser considers it a different file but prevents the need to change the file name.
Refactoring the functions to return promises and using async/await
is usually the best option. Instead of supplying the functions with callbacks that cause deep nesting, they return a promise that can be await
ed and will be resolved once the data has arrived, allowing the next line of code to be evaluated in a sync-like fashion. The above code can be restructured like so:
There are lots of ways to solve the issue of callback hells:
Modularization: break callbacks into independent functions
Use a control flow library, like async
Use generators with Promises
Use async/await (from v7 on)
As an efficient JavaScript developer, you have to avoid the constantly growing indentation level, produce clean and readable code and be able to handle complex flows.
setState
?The callback function is invoked when setState
has finished and the component gets rendered. Since setState
is asynchronous, the callback function is used for any post action.
The callback function is invoked after setState
finishes and is used for any post action.
It is recommended to use lifecycle method rather this callback function.
Callback refs are preferred over the findDOMNode()
API, due to the fact that findDOMNode()
prevents certain improvements in React in the future.
Callback refs are preferred over findDOMNode()
.
Callbacks are functions passed as an argument to another function to be executed once an event has occurred or a certain task is complete, often used in asynchronous code. Callback functions are invoked later by a piece of code but can be declared on initialization without being invoked. As an example, event listeners are asynchronous callbacks that are only executed when a specific event occurs.
However, callbacks can also be synchronous. The following map
function takes a callback function that is invoked synchronously for each iteration of the loop (array element).
Functions are first-class objects in JavaScript
Callbacks vs Promises
children
prop?children
is part of the props object passed to components that allows components to be passed as data to other components, providing the ability to compose components cleanly. There are a number of methods available in the React API to work with this prop, such as React.Children.map
, React.Children.forEach
, React.Children.count
, React.Children.only
and React.Children.toArray
. A simple usage example of the children prop is as follows:
Children is a prop that allows components to be passed as data to other components.
The React API provides methods to work with this prop.
className
instead of class
like in HTML?React's philosophy in the beginning was to align with the browser DOM API rather than HTML, since that more closely represents how elements are created. Setting a class
on an element meant using the className
API:
Additionally, before ES5, reserved words could not be used in objects:
In IE8, this will throw an error. In modern environments, destructuring will throw an error if trying to assign to a variable:
However, class
can be used as a prop without problems, as seen in other libraries like Preact. React currently allows you to use class
, but will throw a warning and convert it to className
under the hood. There is currently an open thread (as of January 2019) discussing changing className
to class
to reduce confusion.
Using the object spread operator ...
, the object's own enumerable properties can be copied into the new object. This creates a shallow clone of the object.
With this technique, prototypes are ignored. In addition, nested objects are not cloned, but rather their references get copied, so nested objects still refer to the same objects as the original. Deep-cloning is much more complex in order to effectively clone any type of object (Date, RegExp, Function, Set, etc) that may be nested within the object. Other alternatives include:
JSON.parse(JSON.stringify(obj))
can be used to deep-clone a simple object, but it is CPU-intensive and only accepts valid JSON (therefore it strips functions and does not allow circular references).
Object.assign({}, obj)
is another alternative.
Object.keys(obj).reduce((acc, key) => (acc[key] = obj[key], acc), {})
is another more verbose alternative that shows the concept in greater depth.
JavaScript passes objects by reference, meaning that nested objects get their references copied, instead of their values.
The same method can be used to merge two objects.
A closure is a function defined inside another function and has access to its lexical scope even when it is executing outside its lexical scope. The closure has access to variables in three scopes:
Variables declared in its own scope
Variables declared in the scope of the parent function
Variables declared in the global scope In JavaScript, all functions are closures because they have access to the outer scope, but most functions don't utilise the usefulness of closures: the persistence of state. Closures are also sometimes called stateful functions because of this. In addition, closures are the only way to store private data that can't be accessed from the outside in JavaScript. They are the key to the UMD (Universal Module Definition) pattern, which is frequently used in libraries that only expose a public API but keep the implementation details private, preventing name collisions with other libraries or the user's own code.
Closures are useful because they let you associate data with a function that operates on that data.
A closure can substitute an object with only a single method.
Closures can be used to emulate private properties and methods.
Even though two different objects can have the same properties with equal values, they are not considered equal when compared using ==
or ===
. This is because they are being compared by their reference (location in memory), unlike primitive values which are compared by value. In order to test if two objects are equal in structure, a helper function is required. It will iterate through the own properties of each object to test if they have the same values, including nested objects. Optionally, the prototypes of the objects may also be tested for equivalence by passing true
as the 3rd argument. Note: this technique does not attempt to test equivalence of data structures other than plain objects, arrays, functions, dates and primitive values.
Primitives like strings and numbers are compared by their value
Objects on the other hand are compared by their reference (location in memory)
Context provides a way to pass data through the component tree without having to pass props down manually at every level. For example, authenticated user, locale preference, UI theme need to be accessed in the application by many components.
Context provides a way to pass data through a tree of React components, without having to manually pass props.
Context is designed to share data that is considered global for a tree of React components.
Cross-Origin Resource Sharing or CORS is a mechanism that uses additional HTTP headers to grant a browser permission to access resources from a server at an origin different from the website origin. An example of a cross-origin request is a web application served from http://mydomain.com
that uses AJAX to make a request for http://yourdomain.com
. For security reasons, browsers restrict cross-origin HTTP requests initiated by JavaScript. XMLHttpRequest
and fetch
follow the same-origin policy, meaning a web application using those APIs can only request HTTP resources from the same origin the application was accessed, unless the response from the other origin includes the correct CORS headers.
CORS behavior is not an error, it's a security mechanism to protect users.
CORS is designed to prevent a malicious website that a user may unintentionally visit from making a request to a legitimate website to read their personal data or perform actions against their will.
This is a very common question asked during front-end interviews and while it may seem easy, it is critical you know it well!
Shows a solid understanding of spacing and the DOM
CSS preprocessors add useful functionality that native CSS does not have, and generally make CSS neater and more maintainable by enabling DRY (Don't Repeat Yourself) principles. Their terse syntax for nested selectors cuts down on repeated code. They provide variables for consistent theming (however, CSS variables have largely replaced this functionality) and additional tools like color functions (lighten
, darken
, transparentize
, etc), mixins, and loops that make CSS more like a real programming language and gives the developer more power to generate complex CSS.
They allow us to write more maintainable and scalable CSS
Some disadvantages of using CSS preprocessors (setup, re-compilation time can be slow etc.)
The General Sibling Selector ~
selects all elements that are siblings of a specified element. The following example selects all <p>
elements that are siblings of <div>
elements:
The Adjacent Sibling Selector +
selects all elements that are the adjacent siblings of a specified element. The following example will select all <p>
elements that are placed immediately after <div>
elements:
Assuming the browser has already determined the set of rules for an element, each rule is assigned a matrix of values, which correspond to the following from highest to lowest specificity:
Inline rules (binary - 1 or 0)
Number of id selectors
Number of class, pseudo-class and attribute selectors
Number of tags and pseudo-element selectors When two selectors are compared, the comparison is made on a per-column basis (e.g. an id selector will always be higher than any amount of class selectors, as ids have higher specificity than classes). In cases of equal specificity between multiple rules, the rules that comes last in the page's style sheet is deemed more specific and therefore applied to the element.
Specificity matrix: [inline, id, class/pseudo-class/attribute, tag/pseudo-element]
In cases of equal specificity, last rule is applied
Debouncing is a process to add some delay before executing a function. It is commonly used with DOM event listeners to improve the performance of page. It is a technique which allow us to "group" multiple sequential calls in a single one. A raw DOM event listeners can easily trigger 20+ events per second. A debounced function will only be called once the delay has passed.
Common use case is to make API call only when user is finished typing while searching.
The DOM (Document Object Model) is a cross-platform API that treats HTML and XML documents as a tree structure consisting of nodes. These nodes (such as elements and text nodes) are objects that can be programmatically manipulated and any visible changes made to them are reflected live in the document. In a browser, this API is available to JavaScript where DOM nodes can be manipulated to change their styles, contents, placement in the document, or interacted with through event listeners.
The DOM was designed to be independent of any particular programming language, making the structural representation of the document available from a single, consistent API.
The DOM is constructed progressively in the browser as a page loads, which is why scripts are often placed at the bottom of a page, in the <head>
with a defer
attribute, or inside a DOMContentLoaded
event listener. Scripts that manipulate DOM nodes should be run after the DOM has been constructed to avoid errors.
document.getElementById()
and document.querySelector()
are common functions for selecting DOM nodes.
Setting the innerHTML
property to a new value runs the string through the HTML parser, offering an easy way to append dynamic HTML content to a node.
==
and ===
?Triple equals (===
) checks for strict equality, which means both the type and value must be the same. Double equals (==
) on the other hand first performs type coercion so that both operands are of the same type and then applies strict comparison.
Whenever possible, use triple equals to test equality because loose equality ==
can have unintuitive results.
Type coercion means the values are converted into the same type.
Mention of falsy values and their comparison.
An element is a plain JavaScript object that represents a DOM node or component. Elements are pure and never mutated, and are cheap to create. A component is a function or class. Components can have state and take props as input and return an element tree as output (although they can represent generic containers or wrappers and don't necessarily have to emit DOM). Components can initiate side effects in lifecycle methods (e.g. AJAX requests, DOM mutations, interfacing with 3rd party libraries) and may be expensive to create.
Elements are immutable, plain objects that describe the DOM nodes or components you want to render.
Components can be either classes or functions, that take props as an input and return an element tree as the output.
em
and rem
units?Both em
and rem
units are based on the font-size
CSS property. The only difference is where they inherit their values from.
em
units inherit their value from the font-size
of the parent element
rem
units inherit their value from the font-size
of the root element (html
) In most browsers, the font-size
of the root element is set to 16px
by default.
Benefits of using em
and rem
units
Error boundaries are React components that catch JavaScript errors anywhere in their child component tree, log those errors, and display a fallback UI instead of the component tree that crashed. Class components become error boundaries if they define either (or both) of the lifecycle methods static getDerivedStateFromError()
or componentDidCatch().
Error boundaries only catch errors in the components below them in the tree. An error boundary can't catch an error within itself.
https://reactjs.org/docs/error-boundaries.html
Event delegation is a technique of delegating events to a single common ancestor. Due to event bubbling, events "bubble" up the DOM tree by executing any handlers progressively on each ancestor element up to the root that may be listening to it. DOM events provide useful information about the element that initiated the event via Event.target
. This allows the parent element to handle behavior as though the target element was listening to the event, rather than all children of the parent or the parent itself. This provides two main benefits:
It increases performance and reduces memory consumption by only needing to register a single event listener to handle potentially thousands of elements.
If elements are dynamically added to the parent, there is no need to register new event listeners for them. Instead of:
Event delegation involves using a condition to ensure the child target matches our desired element:
The difference between event bubbling and capturing
Event-driven programming is a paradigm that involves building applications that send and receive events. When the program emits events, the program responds by running any callback functions that are registered to that event and context, passing in associated data to the function. With this pattern, events can be emitted into the wild without throwing errors even if no functions are subscribed to it. A common example of this is the pattern of elements listening to DOM events such as click
and mouseenter
, where a callback function is run when the event occurs.
Without the context of the DOM, the pattern may look like this:
With this implementation, on
is the way to subscribe to an event, while emit
is the way to publish the event.
Follows a publish-subscribe pattern.
Responds to events that occur by running any callback functions subscribed to the event.
Show how to create a simple pub-sub implementation with JavaScript.
There are two main syntactic categories in JavaScript: expressions and statements. A third one is both together, referred to as an expression statement. They are roughly summarized as:
Expression: produces a value
Statement: performs an action
Expression statement: produces a value and performs an action A general rule of thumb:
If you can print it or assign it to a variable, it's an expression. If you can't, it's a statement.
Statements appear as instructions that do something but don't produce values.
The only expression in the above code is y >= 0
which produces a value, either true
or false
. A value is not produced by other parts of the code.
Expressions produce a value. They can be passed around to functions because the interpreter replaces them with the value they resolve to.
There is an equivalent version of the set of statements used before as an expression using the conditional operator:
This is both an expression and a statement, because we are declaring a variable x
(statement) as an evaluation (expression).
Function declarations vs function expressions
A value is either truthy or falsy depending on how it is evaluated in a Boolean context. Falsy means false-like and truthy means true-like. Essentially, they are values that are coerced to true
or false
when performing certain operations. There are 6 falsy values in JavaScript. They are:
false
undefined
null
""
(empty string)
NaN
0
(both +0
and -0
) Every other value is considered truthy. A value's truthiness can be examined by passing it into the Boolean
function.
There is a shortcut for this using the logical NOT !
operator. Using !
once will convert a value to its inverse boolean equivalent (i.e. not false is true), and !
once more will convert back, thus effectively converting the value to a boolean.
Initialize an empty array of length n
. Use Array.prototype.reduce()
to add values into the array, using the sum of the last two values, except for the first two.
Iterate the array
Alphabetize each word
Store alphabetize word as the key value in a groupedWords object with the original word as the value
Compare alphabetize words to object keys and add additional original words when matches are found
Iterate over the return object and output the values, when there is more then one. (single values mean no anagram )
col-{n} / 12
ratio of the container.Set the .row
parent to display: flex;
and use the flex
shorthand property to give the column classes a flex-grow
value that corresponds to its ratio value.
0.1 + 0.2 === 0.3
evaluate to?It evaluates to false
because JavaScript uses the IEEE 754 standard for Math and it makes use of 64-bit floating numbers. This causes precision errors when doing decimal calculations, in short, due to computers working in Base 2 while decimal is Base 10.
A solution to this problem would be to use a function that determines if two numbers are approximately equal by defining an error margin (epsilon) value that the difference between two values should be less than.
A simple solution to this problem
A focus ring is a visible outline given to focusable elements such as buttons and anchor tags. It varies depending on the vendor, but generally it appears as a blue outline around the element to indicate it is currently focused. In the past, many people specified outline: 0;
on the element to remove the focus ring. However, this causes accessibility issues for keyboard users because the focus state may not be clear. When not specified though, it causes an unappealing blue ring to appear around an element. In recent times, frameworks like Bootstrap have opted to use a more appealing box-shadow
outline to replace the default focus ring. However, this is still not ideal for mouse users. The best solution is an upcoming pseudo-selector :focus-visible
which can be polyfilled today with JavaScript. It will only show a focus ring if the user is using a keyboard and leave it hidden for mouse users. This keeps both aesthetics for mouse use and accessibility for keyboard use.
map()
and forEach()
?Both methods iterate through the elements of an array. map()
maps each element to a new element by invoking the callback function on each element and returning a new array. On the other hand, forEach()
invokes the callback function for each element but does not return a new array. forEach()
is generally used when causing a side effect on each iteration, whereas map()
is a common functional programming technique.
Use forEach()
if you need to iterate over an array and cause mutations to the elements without needing to return values to generate a new array.
map()
is the right choice to keep data immutable where each value of the original array is mapped to a new array.
Fragments allow a React component to return multiple elements without a wrapper, by grouping the children without adding extra elements to the DOM. Fragments offer better performance, lower memory usage, a cleaner DOM and can help in dealing with certain CSS mechanisms (e.g. tables, Flexbox and Grid).
Fragments group multiple elements returned from a component, without adding a DOM element around them.
Functional programming is a paradigm in which programs are built in a declarative manner using pure functions that avoid shared state and mutable data. Functions that always return the same value for the same input and don't produce side effects are the pillar of functional programming. Many programmers consider this to be the best approach to software development as it reduces bugs and cognitive load.
Cleaner, more concise development experience
Simple function composition
Features of JavaScript that enable functional programming (.map
, .reduce
etc.)
JavaScript is multi-paradigm programming language (Object-Oriented Programming and Functional Programming live in harmony)
Single page applications make use of client-side rendering. This means that 'examplesite.com' and 'examplesite.com/page2' are actually the same HTML web page, but the client app decides what content to drop into that single page at runtime. Your user never actually "leaves" the page, and this causes some accessibility issues in terms of focus. Unless focus is explicitly managed in the app, a scenario like this may happen:
User visits 'examplesite.com'
User clicks a link to go to another route: 'examplesite.com/product1'
Client app changes the visible content to show the details for this new route (e.g. some info about Product 1)
Focus is still on the link that was clicked in step 2
If a user uses the keyboard or screen reader to now try and read the content, the focused starting point is in the middle of the page on an element no longer visible Many strategies have been proposed in handling this situation, all involving explicitly managing the focus when the new page content is rendered. Recent research by GatsbyJS suggests the best approach is:
User visits 'examplesite.com'
User clicks a link to go to another route: 'examplesite.com/product1'
Client app changes the visible content to show the details for this new route (e.g. some info about Product 1)
Client app manually places focus on the main header at the top of the page (almost always this will be the H1 element) By doing so, focus is reset to the top of the page, ready for the user to begin exploring the new content. This solution requires inserting the main heading into the start of tabbing order with tabindex="-1"
.
Focus issues caused by client-side rendering, instead of server-side
Focus should not be left on elements no longer visible on the page
Challenges faced by screen reader users and users utilising keyboard navigation
Careful manual focus management required
A higher-order component (HOC) is a function that takes a component as an argument and returns a new component. It is a pattern that is derived from React's compositional nature. Higher-order components are like pure components because they accept any dynamically provided child component, but they won't modify or copy any behavior from their input components.
They can be used for state abstraction and manipulation, props manipulation, render high jacking, etc.
Due to hoisting, the local variable foo
is declared before the console.log
method is called. This means the local variable foo
is passed as an argument to console.log()
instead of the global one declared outside of the function. However, since the value is not hoisted with the variable declaration, the output will be undefined
, not 2
.
Hoisting is JavaScript's default behavior of moving declarations to the top
Mention of strict
mode
Hoisting is a JavaScript mechanism where variable and function declarations are put into memory during the compile phase. This means that no matter where functions and variables are declared, they are moved to the top of their scope regardless of whether their scope is global or local. However, the value is not hoisted with the declaration. The following snippet:
is equivalent to:
Therefore logging hoist
outputs undefined
to the console, not "value"
. Hoisting also allows you to invoke a function declaration before it appears to be declared in a program.
But be wary of function expressions that are assigned to a variable:
Hoisting is JavaScript's default behavior of moving declarations to the top
Functions declarations are hoisted before variable declarations
<header>
elements? What about <footer>
elements?Yes to both. The W3 documents state that the tags represent the header(<header>
) and footer(<footer>
) areas of their nearest ancestor "section". So not only can the page <body>
contain a header and a footer, but so can every <article>
and <section>
element.
W3 recommends having as many as you want, but only 1 of each for each "section" of your page, i.e. body, section etc.
HTML specifications such as HTML5
define a set of rules that a document must adhere to in order to be "valid" according to that specification. In addition, a specification provides instructions on how a browser must interpret and render such a document. A browser is said to "support" a specification if it handles valid documents according to the rules of the specification. As of yet, no browser supports all aspects of the HTML5
specification (although all of the major browser support most of it), and as a result, it is necessary for the developer to confirm whether the aspect they are making use of will be supported by all of the browsers on which they hope to display their content. This is why cross-browser support continues to be a headache for developers, despite the improved specificiations.
HTML5
defines some rules to follow for an invalid HTML5
document (i.e., one that contains syntactical errors)
However, invalid documents may contain anything, so it's impossible for the specification to handle all possibilities comprehensively.
Thus, many decisions about how to handle malformed documents are left up to the browser.
In HTML, the attribute name is in all lowercase and is given a string invoking a function defined somewhere:
In React, the attribute name is camelCase and are passed the function reference inside curly braces:
In HTML, false
can be returned to prevent default behavior, whereas in React preventDefault
has to be called explicitly.
HTML uses lowercase, React uses camelCase.
Some of the key differences are:
An XHTML element must have an XHTML <DOCTYPE>
Attributes values must be enclosed in quotes
Attribute minimization is forbidden (e.g. one has to use checked="checked"
instead of checked
)
Elements must always be properly nested
Elements must always be closed
Special characters must be escaped
Any element can be self-closed
Tags ands attributes are case-sensitive, usually lowercase
<header>
, <article>
,<section>
, <footer>
<header>
is used to contain introductory and navigational information about a section of the page. This can include the section heading, the author's name, time and date of publication, table of contents, or other navigational information.
<article>
is meant to house a self-contained composition that can logically be independently recreated outside of the page without losing its meaning. Individual blog posts or news stories are good examples.
<section>
is a flexible container for holding content that shares a common informational theme or purpose.
<footer>
is used to hold information that should appear at the end of a section of content and contain additional information about the section. Author's name, copyright information, and related links are typical examples of such content.
Other semantic elements are <form>
and <table>
localStorage
and sessionStorage
.With HTML5, web pages can store data locally within the user's browser. The data is stored in name/value pairs, and a web page can only access data stored by itself. Differences between localStorage
and sessionStorage
regarding lifetime:
Data stored through localStorage
is permanent: it does not expire and remains stored on the user's computer until a web app deletes it or the user asks the browser to delete it.
sessionStorage
has the same lifetime as the top-level window or browser tab in which the data got stored. When the tab is permanently closed, any data stored through sessionStorage
is deleted. Differences between localStorage
and sessionStorage
regarding storage scope: Both forms of storage are scoped to the document origin so that documents with different origins will never share the stored objects.
sessionStorage
is also scoped on a per-window basis. Two browser tabs with documents from the same origin have separate sessionStorage
data.
Unlike in localStorage
, the same scripts from the same origin can't access each other's sessionStorage
when opened in different tabs.
Earlier, this was done with cookies.
The storage limit is far larger (at least 5MB) than with cookies and its faster.
The data is never transferred to the server and can only be used if the client specifically asks for it.
This technique is very common in JavaScript libraries. It creates a closure around the entire contents of the file which creates a private namespace and thereby helps avoid potential name clashes between different JavaScript modules and libraries. The function is immediately invoked so that the namespace (library name) is assigned the return value of the function.
Used among many popular JavaScript libraries
Creates a private namespace
These two types of programming can roughly be summarized as:
Imperative: how to achieve something
Declarative: what should be achieved A common example of declarative programming is CSS. The developer specifies CSS properties that describe what something should look like rather than how to achieve it. The "how" is abstracted away by the browser. On the other hand, imperative programming involves the steps required to achieve something. In JavaScript, the differences can be contrasted like so:
We manually loop over the numbers of the array and assign the new index as the number doubled.
We declare that the new array is mapped to a new one where each value is doubled.
Declarative programming often works with functions and expressions. Imperative programming frequently uses statements and relies on low-level features that cause mutations, while declarative programming has a strong focus on abstraction and purity.
Declarative programming is more terse and easier to process at a glance.
Since a JSX element tree is one large expression, you cannot embed statements inside. Conditional expressions act as a replacement for statements to use inside the tree. For example, this won't work:
Logical AND &&
and the ternary ? :
operator replace the if
/else
statements.
Keys are a special string attribute that helps React identify which items have been changed, added or removed. They are used when rendering array elements to give them a stable identity. Each element's key must be unique (e.g. IDs from the data or indexes as a last resort).
Using indexes as keys is not recommended if the order of items may change, as it might negatively impact performance and may cause issues with component state.
If you extract list items as a separate component then apply keys on the list component instead of the <li>
tag.
Keys give elements in a collection a stable identity and help React identify changes.
You should avoid using indexes as keys if the order of items may change.
You should lift the key up to the component, instead of the <li>
element, if you extract list items as components.
Landmark roles is a way to identify different sections of a page like the main content or a navigation region. The Landmarks helps assistive technology users to navigate a page, allowing them skip over areas of it. For example,
Identify sections of a page
Assist users in navigating a page
Lexical scoping refers to when the location of a function's definition determines which variables you have access to. On the other hand, dynamic scoping uses the location of the function's invocation to determine which variables are available.
Lexical scoping is also known as static scoping.
Lexical scoping in JavaScript allows for the concept of closures.
Most languages use lexical scoping because it tends to promote source code that is more easily understood.
getDerivedStateFromProps
: Executed before rendering on the initial mount and all component updates. Used to update the state based on changes in props over time. Has rare use cases, like tracking component animations during the lifecycle. There are only few cases where this makes sense to use over other lifecycle methods. It expects to return an object that will be the the new state, or null to update nothing. This method does not have access to the component instance either. componentDidMount
: Executed after first rendering and here all AJAX requests, DOM or state updates, and set up eventListeners should occur. shouldComponentUpdate
: Determines if the component will be updated or not. By default, it returns true. If you are sure that the component doesn't need to render after state or props are updated, you can return a false value. It is a great place to improve performance as it allows you to prevent a rerender if component receives new prop. getSnapshotBeforeUpdate
: Invoked right after a component render happens because of an update, before componentDidUpdate
. Any value returned from this method will be passed to componentDidUpdate
. componentDidUpdate
: Mostly it is used to update the DOM in response to prop or state changes. componentWillUnmount
: It will be used to cancel any outgoing network requests, or remove all event listeners associated with the component. componentDidCatch
: Used in error boundaries, which are components that implement this method. It allows the component to catch JavaScript errors anywhere in the child component tree (below this component), log errors, and display a UI with error information.
There are four different phases of component's lifecycle: Initialization: In this phase, the component prepares setting up the initial state and default props. Mounting: The react component is ready to mount to the DOM. This phase covers the getDerivedStateFromProps
and componentDidMount
lifecycle methods. Updating: In this phase, the component gets updated in two ways, sending the new props and updating the state. This phase covers the getDerivedStateFromProps
, shouldComponentUpdate
, getSnapshotBeforeUpdate
and componentDidUpdate
lifecycle methods. Unmounting: In this last phase, the component is not needed and gets unmounted from the browser DOM. This phase includes the componentWillUnmount
lifecycle method. Error Handling: In this phase, the component is called whenever there's an error during rendering, in a lifecycle method, or in the constructor for any child component. This phase includes the componentDidCatch
lifecycle method.
When several components need to share the same data, then it is recommended to lift the shared state up to their closest common ancestor. For example, if two child components share the same data, it is recommended to move the shared state to parent instead of maintaining the local state in both child components.
#
except for the last four (4) characters.There are many ways to solve this problem, this is just one one of them. Using
String.prototype.slice()
we can grab the last 4 characters of the string by passing-4
as an argument. Then, usingString.prototype.padStart()
, we can pad the string to the original length with the repeated mask character.
Short, one-line functional solutions to problems should be preferred provided they are efficient
@media
properties?all
, which applies to all media type devices
print
, which only applies to printers
screen
, which only applies to screens (desktops, tablets, mobile etc.)
speech
, which only applies to screenreaders
Memoization is the process of caching the output of function calls so that subsequent calls are faster. Calling the function again with the same input will return the cached output without needing to do the calculation again. A basic implementation in JavaScript looks like this:
The above technique returns a unary function even if the function can take multiple arguments.
The first function call will be slower than usual because of the overhead created by checking if a cached result exists and setting a result before returning the value.
Memoization increases performance on subsequent function calls but still needs to do work on the first call.
this
context in React component classes?In JavaScript classes, the methods are not bound by default. This means that their this
context can be changed (in the case of an event handler, to the element that is listening to the event) and will not refer to the component instance. To solve this, Function.prototype.bind()
can be used to enforce the this
context as the component instance.
The bind
approach can be verbose and requires defining a constructor
, so the new public class fields syntax is generally preferred:
You can also use an inline arrow function, because lexical this
(referring to the component instance) is preserved:
Note that extra re-rendering can occur using this technique because a new function reference is created on render, which gets passed down to child components and breaks shouldComponentUpdate
/ PureComponent
shallow equality checks to prevent unnecessary re-renders. In cases where performance is important, it is preferred to go with bind
in the constructor, or the public class fields syntax approach, because the function reference remains constant.
You can either bind methods to the component instance context in the constructor, use public class fields syntax, or use inline arrow functions.
MIME
is an acronym for Multi-purpose Internet Mail Extensions
. It is used as a standard way of classifying file types over the Internet.
A MIME type
actually has two parts: a type and a subtype that are separated by a slash (/). For example, the MIME type
for Microsoft Word files is application/msword
(i.e., type is application and the subtype is msword).
The two terms can be contrasted as:
Mutable: subject to change
Immutable: cannot change In JavaScript, objects are mutable while primitive values are immutable. This means operations performed on objects can change the original reference in some way, while operations performed on a primitive value cannot change the original value. All String.prototype
methods do not have an effect on the original string and return a new string. On the other hand, while some methods of Array.prototype
do not mutate the original array reference and produce a fresh array, some cause mutations.
List of mutating and non-mutating array methods
NaN
(Not-a-Number) is the only value not equal to itself when comparing with any of the comparison operators. NaN
is often the result of meaningless math computations, so two NaN
values make no sense to be considered equal.
The difference between isNaN()
and Number.isNaN()
const isNaN = x => x !== x
Advantages include:
Not needing to process data if there is no need to even reference it
Having a consistent API leads to more adoption
Ability to easily adapt a callback pattern that will lead to more maintainable code As you can see from below example, the callback is called with null as its first argument if there is no error. However, if there is an error, you create an Error object, which then becomes the callback's only parameter. The callback function allows a user to easily know whether or not an error occurred. This practice is also called the Node.js error convention, and this kind of callback implementations are called error-first callbacks.
This is just a convention. However, you should stick to it.
The event loop handles all async callbacks. Callbacks are queued in a loop, while other code runs, and will run one by one when the response for each one has been received.
The event loop allows Node.js to perform non-blocking I/O operations, despite the fact that JavaScript is single-threaded
null
and undefined
?In JavaScript, two values discretely represent nothing - undefined
and null
. The concrete difference between them is that null
is explicit, while undefined
is implicit. When a property does not exist or a variable has not been given a value, the value is undefined
. null
is set as the value to explicitly indicate "no value". In essence, undefined
is used when the nothing is not known, and null
is used when the nothing is known.
typeof undefined
evaluates to "undefined"
.
typeof null
evaluates "object"
. However, it is still a primitive value and this is considered an implementation bug in JavaScript.
undefined == null
evaluates to true
.
Often used to store one occurrence of data.
Often used when you need to create multiple instances of an object, each with their own data that other instances of the class cannot affect. The new
operator must be used before invoking the constructor or the global object will be mutated.
Creates a new object similar to a constructor, but can store private data using a closure. There is also no need to use new
before invoking the function or the this
keyword. Factory functions usually discard the idea of prototypes and keep all properties and methods as own properties of the object.
Object.create()
Sets the prototype of the newly created object.
A second argument can also be supplied to Object.create()
which acts as a descriptor for the new properties to be defined.
Prototypes are objects that other objects inherit properties and methods from.
Factory functions offer private properties and methods through a closure but increase memory usage as a tradeoff, while classes do not have private properties or methods but reduce memory impact by reusing a single prototype object.
Parameters are the variable names of the function definition, while arguments are the values given to a function when it is invoked.
arguments
is an array-like object containing information about the arguments supplied to an invoked function.
myFunction.length
describes the arity of a function (how many parameters it has, regardless of how many arguments it is supplied).
JavaScript always passes by value. However, with objects, the value is a reference to the object.
Difference between pass-by-value and pass-by-reference
You can use an arrow function to wrap around an event handler and pass arguments, which is equivalent to calling bind
:
pipe
that performs left-to-right function composition by returning a function that accepts one argument.Gather all supplied arguments using the rest operator ...
and return a unary function that uses Array.prototype.reduce()
to run the value through the series of functions before returning the final value.
Function composition is the process of combining two or more functions to produce a new function.
Portal are the recommended way to render children into a DOM node that exists outside the DOM hierarchy of the parent component.
The first argument (child
) is any renderable React child, such as an element, string, or fragment. The second argument (container
) is a DOM element.
i++
and prefix ++i
increment operators?Both increment the variable value by 1. The difference is what they evaluate to. The postfix increment operator evaluates to the value before it was incremented.
The prefix increment operator evaluates to the value after it was incremented.
A Promise
is in one of these states:
pending: initial state, neither fulfilled nor rejected.
fulfilled: meaning that the operation completed successfully.
rejected: meaning that the operation failed. A pending promise can either be fulfilled with a value, or rejected with a reason (error). When either of these options happens, the associated handlers queued up by a promise's then method are called.
The Promise
object represents the eventual completion (or failure) of an asynchronous operation, and its resulting value. An example can be the following snippet, which after 100ms prints out the result string to the standard output. Also, note the catch, which can be used for error handling. Promise
s are chainable.
Take a look into the other questions regarding Promise
s!
When the application is running in development mode, React will automatically check for all props that we set on components to make sure they are the correct data type. For incorrect data types, it will generate warning messages in the console for development mode. They are stripped in production mode due to their performance impact. Required props are defined with isRequired
. For example, we define propTypes
for component as below:
We can define custom propTypes
Using propTypes
is not mandatory. However, it is a good practice and can reduce bugs.
In the classical inheritance paradigm, object instances inherit their properties and functions from a class, which acts as a blueprint for the object. Object instances are typically created using a constructor and the new
keyword. In the prototypal inheritance paradigm, object instances inherit directly from other objects and are typically created using factory functions or Object.create()
.
A pure function is a function that satisfies these two conditions:
Given the same input, the function returns the same output.
The function doesn't cause side effects outside of the function's scope (i.e. mutate data outside the function or data supplied to the function). Pure functions can mutate local data within the function as long as it satisfies the two conditions above.
Pure functions are easier to reason about due to their reliability.
All functions should be pure unless explicitly causing a side effect (i.e. setInnerHTML
).
If a function does not return a value, it is an indication that it is causing side effects.
Comments must be wrapped inside curly braces {}
and use the /* */
syntax.
Recursion is the repeated application of a process. In JavaScript, recursion involves functions that call themselves repeatedly until they reach a base condition. The base condition breaks out of the recursion loop because otherwise the function would call itself indefinitely. Recursion is very useful when working with data structures that contain nesting where the number of levels deep is unknown. For example, you may have a thread of comments returned from a database that exist in a flat array but need to be nested for display in the UI. Each comment is either a top-level comment (no parent) or is a reply to a parent comment. Comments can be a reply of a reply of a reply… we have no knowledge beforehand the number of levels deep a comment may be. This is where recursion can help.
In the above example, the base condition is met if filter()
returns an empty array. The chained map()
won't invoke the callback function which contains the recursive call, thereby breaking the loop.
Recursion is useful when working with data structures containing an unknown number of nested structures.
Recursion must have a base condition to be met that breaks out of the loop or it will call itself indefinitely.
The first console.log
outputs true
because JavaScript's compiler performs type conversion and therefore it compares to strings by their value. On the other hand, the second console.log
outputs false
because Arrays are Objects and Objects are compared by reference.
JavaScript performs automatic type conversion
Objects are compared by reference
Primitives are compared by value
Refs provide a way to access DOM nodes or React elements created in the render method. Refs should be used sparringly, but there are some good use cases for refs, such as:
Managing focus, text selection, or media playback.
Triggering imperative animations.
Integrating with third-party DOM libraries. Refs are created using React.createRef()
method and attached to React elements via the ref
attribute. In order to use refs throughout the component, assign the ref
to the instance property within the constructor:
Refs can also be used in functional components with the help of closures.
Refs are used to return a reference to an element.
Refs shouldn't be overused.
You can create a ref using React.createRef()
and attach to elements via the ref
attribute.
rel="noopener"
attribute used?The rel="noopener"
is an attribute used in <a>
elements (hyperlinks). It prevents pages from having a window.opener
property, which would otherwise point to the page from where the link was opened and would allow the page opened from the hyperlink to manipulate the page where the hyperlink is.
rel="noopener"
is applied to hyperlinks.
rel="noopener"
prevents opened links from manipulating the source page.
REST (REpresentational State Transfer) is a software design pattern for network architecture. A RESTful web application exposes data in the form of information about its resources. Generally, this concept is used in web applications to manage state. With most applications, there is a common theme of reading, creating, updating, and destroying data. Data is modularized into separate tables like posts
, users
, comments
, and a RESTful API exposes access to this data with:
An identifier for the resource. This is known as the endpoint or URL for the resource.
The operation the server should perform on that resource in the form of an HTTP method or verb. The common HTTP methods are GET, POST, PUT, and DELETE. Here is an example of the URL and HTTP method with a posts
resource:
Reading: /posts/
=> GET
Creating: /posts/new
=> POST
Updating: /posts/:id
=> PUT
Destroying: /posts/:id
=> DELETE
Alternatives to this pattern like GraphQL
Because of JavaScript's automatic semicolon insertion (ASI), the compiler places a semicolon after return
keyword and therefore it returns undefined
without an error being thrown.
Automatic semicolon placement can lead to time-consuming bugs
Automatic semicolon insertion in JavaScript Folders <parent>
File | File | File | File | File | File |
accessibility-aria.html | fibonacci.html | object-creation.html | accessibility-contrast.md | fibonacci.md | null-vs-undefined.md |
accessibility-contrast.html | find-the-anagrams.html | parameter-vs-argument.html | accessibility-testing.md | find-the-anagrams.md | object-creation.md |
accessibility-testing.html | flex-layout.html | pass-by-value-reference.html | accessibility-tree.md | flex-layout.md | parameter-vs-argument.md |
accessibility-tree.html | floating-point.html | passing-arguments-to-event-handlers.html | alt-attribute.md | floating-point.md | pass-by-value-reference.md |
alt-attribute.html | focus-ring.html | pipe.html | async-defer-attributes.md | focus-ring.md | passing-arguments-to-event-handlers.md |
async-defer-attributes.html | for-each-map.html | portals.html | async-functions.md | for-each-map.md | pipe.md |
async-functions.html | fragments.html | postfix-vs-prefix-increment.html | batches.md | fragments.md | portals.md |
batches.html | functional-programming.html | promise-states.html | bem.md | functional-programming.md | postfix-vs-prefix-increment.md |
bem.html | handling-route-changes-in-single-page-apps.html | promises.html | big-o-notation.md | handling-route-changes-in-single-page-apps.md | promise-states.md |
big-o-notation.html | hoc-component.html | prop-validation.html | bind-function.md | hoc-component.md | promises.md |
bind-function.html | hoisting-example.html | prototypal-inheritance.html | cache-busting.md | hoisting-example.md | prop-validation.md |
cache-busting.html | hoisting.html | pure-functions.html | callback-hell.md | hoisting.md | prototypal-inheritance.md |
callback-hell.html | html-multiple-header-footers.html | react-comments.html | callback-in-setState.md | html-multiple-header-footers.md | pure-functions.md |
callback-in-setState.html | html-specification-implementation.html | recursion.html | callback-refs-vs-finddomnode.md | html-specification-implementation.md | react-comments.md |
callback-refs-vs-finddomnode.html | html-vs-react-event-handling.html | reference-example.html | callbacks.md | html-vs-react-event-handling.md | recursion.md |
callbacks.html | html-vs-xhtml.html | refs.html | children-prop.md | html-vs-xhtml.md | reference-example.md |
children-prop.html | html5-semantic-elements-usage.html | rel-noopener.html | class-name.md | html5-semantic-elements-usage.md | refs.md |
class-name.html | html5-web-storage.html | rest.html | clone-object.md | html5-web-storage.md | rel-noopener.md |
clone-object.html | iife.html | return-semicolon.html | closures.md | iife.md | rest.md |
closures.html | imperative-vs-declarative.html | right.html | comparing-objects.md | imperative-vs-declarative.md | return-semicolon.md |
comparing-objects.html | inline-conditional-expressions.html | semicolons.html | context.md | inline-conditional-expressions.md | semicolons.md |
context.html | keys.html | short-circuit-evaluation.html | cors.md | keys.md | short-circuit-evaluation.md |
cors.html | landmark-roles.html | sprites.html | css-box-model.md | landmark-roles.md | sprites.md |
css-box-model.html | lexical-vs-dynamic-scoping.html | stateful-components.html | css-preprocessors.md | lexical-vs-dynamic-scoping.md | stateful-components.md |
css-preprocessors.html | lifecycle-methods.html | stateless-components.html | css-sibling-selectors.md | lifecycle-methods.md | stateless-components.md |
css-sibling-selectors.html | lifecycle.html | static-vs-instance-method.html | css-specificity.md | lifecycle.md | static-vs-instance-method.md |
css-specificity.html | lift-state.html | sync-vs-async.html | debouncing.md | lift-state.md | sync-vs-async.md |
debouncing.html | mask.html | this.html | dom.md | mask.md | this.md |
dom.html | media-properties.html | typeof-typeof.html | double-vs-triple-equals.md | media-properties.md | typeof-typeof.md |
double-vs-triple-equals.html | memoize.html | types.html | element-vs-component.md | memoize.md | types.md |
element-vs-component.html | methods-context-react-classes.html | ui-library-framework-purpose.html | em-rem-difference.md | methods-context-react-classes.md | ui-library-framework-purpose.md |
em-rem-difference.html | mime.html | use-strict.html | error-boundaries.md | mime.md | use-strict.md |
error-boundaries.html | mutable-vs-immutable.html | var-let-const.html | event-delegation.md | mutable-vs-immutable.md | var-let-const.md |
event-delegation.html | nan.html | virtual-dom.html | event-driven-programming.md | nan.md | virtual-dom.md |
event-driven-programming.html | node-error-first-callback.html | wcag.html | expression-vs-statement.md | node-error-first-callback.md | wcag.md |
expression-vs-statement.html | node-event-loop.html | xss.html | falsy-truthy.md | node-event-loop.md | xss.md |
falsy-truthy.html | null-vs-undefined.html | accessibility-aria.md | |||
Folders: 1 | |||||
Files: 219 | |||||
Size of all files: 461594 K |
Sometimes. Due to JavaScript's automatic semicolon insertion, the interpreter places semicolons after most statements. This means semicolons can be omitted in most cases. However, there are some cases where they are required. They are not required at the beginning of a block, but are if they follow a line and:
The line starts with [
The line starts with (
In the above cases, the interpreter does not insert a semicolon after 3
, and therefore it will see the 3
as attempting object property access or being invoked as a function, which will throw errors.
Semicolons are usually optional in JavaScript but have edge cases where they are required.
If you don't use semicolons, tools like Prettier will insert semicolons for you in the places where they are required on save in a text editor to prevent errors.
Short-circuit evaluation involves logical operations evaluating from left-to-right and stopping early.
In the above sample using logical OR, JavaScript won't look at the second operand false
, because the expression evaluates to true
regardless. This is known as short-circuit evaluation. This also works for logical AND.
This means you can have an expression that throws an error if evaluated, and it won't cause issues.
This remains true for multiple operations because of left-to-right evaluation.
A common use case for this behavior is setting default values. If the first operand is falsy the second operand will be evaluated.
Another common use case is only evaluating an expression if the first operand is truthy.
In the above case, if e.target
is not or does not contain an element matching the "button"
selector, the function will not be called. This is because the first operand will be falsy, causing the second operand to not be evaluated.
Logical operations do not produce a boolean unless the operand(s) evaluate to a boolean.
CSS sprites combine multiple images into one image, limiting the number of HTTP requests a browser has to make, thus improving load times. Even under the new HTTP/2 protocol, this remains true. Under HTTP/1.1, at most one request is allowed per TCP connection. With HTTP/1.1, modern browsers open multiple parallel connections (between 2 to 8) but it is limited. With HTTP/2, all requests between the browser and the server are multiplexed on a single TCP connection. This means the cost of opening and closing multiple connections is mitigated, resulting in a better usage of the TCP connection and limits the impact of latency between the client and server. It could then become possible to load tens of images in parallel on the same TCP connection. However, according to benchmark results, although HTTP/2 offers 50% improvement over HTTP/1.1, in most cases the sprite set is still faster to load than individual images. To utilize a spritesheet in CSS, one would use certain properties, such as background-image
, background-position
and background-size
to ultimately alter the background
of an element.
background-image
, background-position
and background-size
can be used to utilize a spritesheet.
A stateful component is a component whose behavior depends on its state. This means that two separate instances of the component if given the same props will not necessarily render the same output, unlike pure function components.
Stateful components have internal state that they depend on.
Stateful components are class components or function components that use stateful Hooks.
Stateful components have their state initialized in the constructor or with useState()
.
A stateless component is a component whose behavior does not depend on its state. Stateless components can be either functional or class components. Stateless functional components are easier to maintain and test since they are guaranteed to produce the same output given the same props. Stateless functional components should be preferred when lifecycle hooks don't need to be used.
Stateless components are independent of their state.
Stateless components can be either class or functional components.
Stateless functional components avoid the this
keyword altogether.
Static methods belong to a class and don't act on instances, while instance methods belong to the class prototype which is inherited by all instances of the class and acts on them.
In this case, the Array.isArray
method does not make sense as an instance method of arrays because we already know the value is an array when working with it. Instance methods could technically work as static methods, but provide terser syntax:
How to create static and instance methods with ES2015 class syntax
Synchronous means each operation must wait for the previous one to complete. Asynchronous means an operation can occur while another operation is still being processed. In JavaScript, all code is synchronous due to the single-threaded nature of it. However, asynchronous operations not part of the program (such as XMLHttpRequest
or setTimeout
) are processed outside of the main thread because they are controlled by native code (browser APIs), but callbacks part of the program will still be executed synchronously.
JavaScript has a concurrency model based on an "event loop".
Functions like alert
block the main thread so that no user input is registered until the user closes it.
this
keyword and how does it work?The this
keyword is an object that represents the context of an executing function. Regular functions can have their this
value changed with the methods call()
, apply()
and bind()
. Arrow functions implicitly bind this
so that it refers to the context of its lexical environment, regardless of whether or not its context is set explicitly with call()
. Here are some common examples of how this
works:
this
refers to the object itself inside regular functions if the object precedes the invocation of the function. Properties set as this
do not refer to the object.
this
refers to the element listening to the event.
this
refers to the newly created object.
With call()
and apply()
, this
refers to the object passed as the first argument.
this
Because this
can change depending on the scope, it can have unexpected values when using regular functions.
In non-strict mode, global this
is the global object (window
in browsers), while in strict mode global this
is undefined
.
Function.prototype.call
and Function.prototype.apply
set the this
context of an executing function as the first argument, with call
accepting a variadic number of arguments thereafter, and apply
accepting an array as the second argument which are fed to the function in a variadic manner.
Function.prototype.bind
returns a new function that enforces the this
context as the first argument which cannot be changed by other functions.
If a function requires its this
context to be changed based on how it is called, you must use the function
keyword. Use arrow functions when you want this
to be the surrounding (lexical) context.
It evaluates to "string"
. typeof 0
evaluates to the string "number"
and therefore typeof "number"
evaluates to "string"
.
The latest ECMAScript standard defines seven data types, six of them being primitive: Boolean
, Null
, Undefined
, Number
, String
, Symbol
and one non-primitive data type: Object
.
Mention of newly added Symbol
data type
Array
, Date
and function
are all of type object
Functions in JavaScript are objects with the capability of being callable
The main purpose is to avoid manipulating the DOM directly and keep the state of an application in sync with the UI easily. Additionally, they provide the ability to create components that can be reused when they have similar functionality with minor differences, avoiding duplication which would require multiple changes whenever the structure of a component which is reused in multiple places needs to be updated. When working with DOM manipulation libraries like jQuery, the data of an application is generally kept in the DOM itself, often as class names or data
attributes. Manipulating the DOM to update the UI involves many extra steps and can introduce subtle bugs over time. Keeping the state separate and letting a framework handle the UI updates when the state changes reduces cognitive load. Saying you want the UI to look a certain way when the state is a certain value is the declarative way of creating an application, instead of the imperative way of manually updating the UI to reflect the new state.
The virtual DOM is a representation of the real DOM tree in the form of plain objects, which allows a library to write code as if the entire document is thrown away and rebuilt on each change, while the real DOM only updates what needs to be changed. Comparing the new virtual DOM against the previous one leads to high efficiency as changing real DOM nodes is costly compared to recalculating the virtual DOM.
JSX is an extension to JavaScript that provides XML-like syntax to create virtual DOM objects which is transformed to function calls by a transpiler. It simplifies control flow (if statements/ternary expressions) compared to tagged template literals.
'use strict'
do and what are some of the key benefits to using it?Including 'use strict'
at the beginning of your JavaScript source file enables strict mode, which enforces more strict parsing and error handling of JavaScript code. It is considered a good practice and offers a lot of benefits, such as:
Easier debugging due to eliminating silent errors.
Disallows variable redefinition.
Prevents accidental global variables.
Oftentimes provides increased performance over identical code that is not running in strict mode.
Simplifies eval()
and arguments
.
Helps make JavaScript more secure.
Eliminates this
coercion, throwing an error when this
references a value of null
or undefined
.
Throws an error on invalid usage of delete
.
Prohibits some syntax likely to be defined in future versions of ECMAScript
var
, let
, const
and no keyword statements?When no keyword exists before a variable assignment, it is either assigning a global variable if one does not exist, or reassigns an already declared variable. In non-strict mode, if the variable has not yet been declared, it will assign the variable as a property of the global object (window
in browsers). In strict mode, it will throw an error to prevent unwanted global variables from being created.
var
was the default statement to declare a variable until ES2015. It creates a function-scoped variable that can be reassigned and redeclared. However, due to its lack of block scoping, it can cause issues if the variable is being reused in a loop that contains an asynchronous callback because the variable will continue to exist outside of the block scope. Below, by the time the the setTimeout
callback executes, the loop has already finished and the i
variable is 10
, so all ten callbacks reference the same variable available in the function scope.
let
was introduced in ES2015 and is the new preferred way to declare variables that will be reassigned later. Trying to redeclare a variable again will throw an error. It is block-scoped so that using it in a loop will keep it scoped to the iteration.
const
was introduced in ES2015 and is the new preferred default way to declare all variables if they won't be reassigned later, even for objects that will be mutated (as long as the reference to the object does not change). It is block-scoped and cannot be reassigned.
All declarations are hoisted to the top of their scope.
However, with let
and const
there is a concept called the temporal dead zone (TDZ). While the declarations are still hoisted, there is a period between entering scope and being declared where they cannot be accessed.
Show a common issue with using var
and how let
can solve it, as well as a solution that keeps var
.
var
should be avoided whenever possible and prefer const
as the default declaration statement for all variables unless they will be reassigned later, then use let
if so.
The virtual DOM (VDOM) is a representation of the real DOM in the form of plain JavaScript objects. These objects have properties to describe the real DOM nodes they represent: the node name, its attributes, and child nodes.
The above markup's virtual DOM representation might look like this:
The library/framework uses the virtual DOM as a means to improve performance. When the state of an application changes, the real DOM needs to be updated to reflect it. However, changing real DOM nodes is costly compared to recalculating the virtual DOM. The previous virtual DOM can be compared to the new virtual DOM very quickly in comparison. Once the changes between the old VDOM and new VDOM have been calculated by the diffing engine of the framework, the real DOM can be patched efficiently in the least time possible to match the new state of the application.
Why accessing the DOM can be so costly.
WCAG stands for "Web Content Accessibility Guidelines". It is a standard describing how to make web content more accessible to people with disabilities They have 12-13 guidelines and for each one, there are testable success criteria, which are at three levels: A, AA, and AAA. The higher the level, the higher the impact on the design of the web content. The higher the level, the web content is essentially more accessible by more users. Depending on where you live/work, there may be regulations requiring websites to meet certain levels of compliance. For instance, in Ontario, Canada, beginning January 1, 2021 all public websites and web content posted after January 1, 2012 must meet AA compliance.
A guideline for making web content more accessible
3 different levels (A, AA, and AAA) of compliance for each guideline
Governments are starting to require web content to meet a certain level of compliance by law
XSS refers to client-side code injection where the attacker injects malicious scripts into a legitimate website or web application. This is often achieved when the application does not validate user input and freely injects dynamic HTML content. For example, a comment system will be at risk if it does not validate or escape user input. If the comment contains unescaped HTML, the comment can inject a <script>
tag into the website that other users will execute against their knowledge.
The malicious script has access to cookies which are often used to store session tokens. If an attacker can obtain a user's session cookie, they can impersonate the user.
The script can arbitrarily manipulate the DOM of the page the script is executing in, allowing the attacker to insert pieces of content that appear to be a real part of the website.
The script can use AJAX to send HTTP requests with arbitrary content to arbitrary destinations.
On the client, using textContent
instead of innerHTML
prevents the browser from running the string through the HTML parser which would execute scripts in it.
On the server, escaping HTML tags will prevent the browser from parsing the user input as actual HTML and therefore won't execute the script.
Content
: The inner-most part of the box filled with content, such as text, an image, or video player. It has the dimensions content-box width
and content-box height
. Padding
: The transparent area surrounding the content. It has dimensions padding-box width
and padding-box height
. Border
: The area surrounding the padding (if any) and content. It has dimensions border-box width
and border-box height
. Margin: The transparent outer-most layer that surrounds the border. It separates the element from other elements in the DOM. It has dimensions margin-box width
and margin-box height
. alt text