5 Fancy Concepts Everyone Assumes You Know as a Frontend Engineer

While reading documents, we usually come across different terminology or words that are rarely explained right in the documents, often because they are well-known and assumed to be understood already. But that’s not true. As a non-native English speaker or someone who hasn’t spent 4 years in computer science major, you can easily stump upon concepts that sound like alien language. In this article, I collect a list of such concepts and try to explain it as simple as possible.

Pure function and side effect

It’s hard to explain these 2 concepts separately as their definition depends on each other. So I’ll try to explain them informally without mention the other word.

Pure function is a function that satisfies 2 characteristics:

  • Given the same input, it always returns the same output
  • It doesn’t mutate any objects or variables that exists before it is called, or produce any consequence outside of its primary purpose, which is to read the function arguments, do calculation and spit out the result.

And guess what, side effects are exactly those things - anything beyond taking inputs and returning outputs. Mutating an object or a variable outside of function is a side effect. Fetching user data from an API endpoint is a side effect. Saving data to database is a side effect.

// This is your "pure" world - predictable, testworthy, lovely!
function add(a, b) {
  return a + b;
}

// Welcome to the "side effects" world - where things get interesting...
async function getUserAndSaveToDatabase(userId) {
  const user = await fetchUser(userId); // Talk to outside world? Side effect!
  await saveToDatabase(user);           // Modify something outside? Side effect!
  console.log('User saved! 🎉');        // Even logging is a side effect!
  return user;
}

Why does this matter? In simple terms, if something is pure, it’s generally easier to reason about. It’s also easier to test, you just need to provide the input values and check if the returned result equal an expected value. On the other hand, side effects are harder to work with. Mutating a global object or variable might cause subtle bugs. Testing side effect requires mocking. Order in which side effects happen matters. In conclusion, we try to apply pure functions or make things pure as much as possible, and be intentional about when and where side effects happen.

Screenshot, pure function in React doc

Screenshot, pure function in React doc

Screenshot, side effect in React doc

Screenshot, side effect in React doc

Thunk

You might already hear about Redux middleware redux-thunk. So what is that name originates from? What is a thunk?

Formally, it is defined as “a piece of code that does some delayed work”. I say Thunk is a piece of code that allow you to define logic to execute and then use it later in the future. For example, this is a thunk:

// Regular function
const multiply = (a, b) => a * b;

// Thunk, define the logic
const multiplyThunk = () => {
    return (a, b) => a * b;
};

// here we have the logic before hand
const multiplicationLogic = mulitplyThunk();

// ...some time later we use it:
const result = multiplicationLogic(a, b);

In redux-thunk context, you define the logic that interacts with dispatch and getState to be run later, for example when users click a button.

// fetchTodoById is the "thunk action creator"
export function fetchTodoById(todoId) {
  // fetchTodoByIdThunk is the "thunk function"
  return async function fetchTodoByIdThunk(dispatch, getState) {
    // the logic I want to run later, when users click the button is:
    // fetch todo
    // then dispatch an action
    const response = await client.get(`/fakeApi/todo/${todoId}`)
    dispatch(todosLoaded(response.todos))
  }
}

Why does this matter? It allows you to define a reusable logic. In the above example, the logic to fetch todo by id, no matter which id, is the same and reused for all todos. It also helps testing easier.

// Mock the API client
jest.mock('./client', () => ({
  get: jest.fn()
}))

...
// Mock dispatch and getState
const dispatch = jest.fn()
const getState = jest.fn()
const fakeTodos = [{ id: 1, text: 'Buy milk' }]
client.get.mockResolvedValue({ todos: fakeTodos })

// Act
const thunk = fetchTodoById(1)
await thunk(dispatch, getState)

// Assert
expect(client.get).toHaveBeenCalledWith('/fakeApi/todo/1')
expect(dispatch).toHaveBeenCalledWith(todosLoaded(fakeTodos))
Screenshot, thunk in Redux doc

Screenshot, thunk in Redux doc

Serializable vs non-serializable

Serialization is formally defined as the process of converting the state of an object into a form that can be persisted or transported. For frontend world, this usually means anything than can be converted into string and sent over the network.

It’s that simple! If you can convert it to a string (like JSON) and back without losing anything, it’s serializable.

// Serializable - totally cool! 😎
const user = {
  name: 'Dan',
  age: 30,
  likes: ['React', 'Redux', 'JavaScript']
}

const stringifiedUser = JSON.stringify(user); // '{"name":"Dan","age":30,"likes":["React","Redux","JavaScript"]}'

// Non-serializable - nope! 🚫
const dangerousStuff = {
  sayHi: () => console.log('hi!'),  // Functions can't be serialized!
  domNode: document.body,           // DOM nodes? Forget about it!
  circular: {},                     // Circular references? No way!
}

const stringifiedDangerousStuff = JSON.stringify(dangerousStuff);
// '{"domNode":{},"circular":{}}' --> loosing information

dangerousStuff.circular.self = dangerousStuff  // 🌀 Infinite loop if you try to JSON.stringify!

const stringifiedDangerousStuffCircular = JSON.stringify(dangerousStuff);
// --> throw error

This matters a ton in Redux because we want our state to be something we can save, reload, or send to other parts of our app, or even over the network for hydration. That’s why we get cranky when people try to put functions or DOM nodes in their Redux state.

Screenshot, non-serializable in Redux doc

Screenshot, non-serializable in Redux doc

Screenshot, serializable in React doc

Screenshot, serializable in React doc

Idempotent

You know when you press the “Stop” button on a cassette player? Whether you press it once or smash it five times in frustration, the result is the same — the tape stops! That’s idempotent.

Formally, an action is said to be idempotent if executing the same action once or more than once produce the same result. In other word, the result is independent of the number of times the action is executed.

// Idempotent operations - do it once or many times, same result! 😎
const numbers = [2, 2, 3, 3, 3];
const uniqueNumbers = new Set(numbers);         // [2, 3]
const alsoUniqueNumbers = new Set(uniqueNumbers); // Still [2, 3]!

// Redux reducers should be idempotent too!
function todoReducer(state, action) {
  switch (action.type) {
    case 'todos/setComplete':
      return {
        ...state,
        completed: true  // No matter how many times, it's just... true!
      }
  }
}

// Not idempotent - danger zone! 🚨
const count = 0;
count += 1;  // 1
count += 1;  // 2
count += 1;  // 3 - Different result each time!

This term matters in many situations.

It’s super important in distributed systems and APIs because things might happen more than once (like when users double-click a submit button). If your operation is idempotent, you don’t need to worry about it.

HTTP PUT is supposed to be idempotent (setting a value) but POST usually isn’t (creating new stuff).

A script to migrate data is recommended to be idempotent so it can be safely run multiple times in case there’re issue, maybe due to network, and need to be retried.

Screenshot, idempotent in Shopify doc

Screenshot, idempotent in Shopify doc

Stateful vs stateless

Imagine a whiteboard.

Stateless: Every time you ask a question, the whiteboard is wiped clean. The answer depends only on what you just asked.

Stateful: The whiteboard keeps track of what’s written. Every question builds on the notes left from before, so past interactions matter.

Stateless has no memory. Stateful remembers stuff.

Another example. Imagine you’re at a coffee shop.

Stateless: Every time you order, the barista asks: What’s your name? What do you want? They forget everything after your order. Each interaction is independent.

Stateful: Now the barista uses a loyalty card. Swipe it, and they remember your name and order from last time so you can have discount.

Why does this matter? Choosing or designing a component, a system or an API to be stateful and stateless will have different consequences.

A RESTful API is defined to be stateless because stateless help increase scalability. If one server can handle ten thousand requests, to handle one hundred thousand requests, you just need to duplicate ten exact server with the same config and setup. Any server can serve any request as there’s no dependency between multiple requests.

A JavaScript RegExp object is stateful.

const regexTest = /test/g;

regexTest.exec("test test")
// --> ['test', index: 0, input: 'test test', groups: undefined]

regexTest.exec("test test")
// --> ['test', index: 5, input: 'test test', groups: undefined]

regexTest.exec("test test")
// --> null

Knowing this can help you debug and avoid weird bugs when using regular expression in Javascript.

Screenshot, stateful in MDN doc

Screenshot, stateful in MDN doc