diff --git a/chapters/ch02.asciidoc b/chapters/ch02.asciidoc
index 13051af..edbbe96 100644
--- a/chapters/ch02.asciidoc
+++ b/chapters/ch02.asciidoc
@@ -285,7 +285,7 @@ When a commonality can be found, abstractions involve less friction and help avo
 
 Another case where abstractions come in handy might be whenever we are dealing with quirks in cross-browser DOM manipulation. Having a function like `on(element, eventType, eventListener)` would be superior than testing whether `addEventListener` is supported and deciding which of the various event listening options is optimal for each case, every time, as it drastically reduces code duplication while also handling every case consistently, limiting complexity.
 
-The above are clear-cut examples of cases when an abstraction greatly improves poor interfaces, but that's not always the end result. Abstractions can be a costly way of merging use cases when it's unclear whether those are naturally related in the first place. If we merge use cases too early, we might find that the the complexity we're tucking away in an abstraction is quite small -- and thus offset by the abstraction's own complexity. If we merge cases which weren't all that related to begin with, we'd be effectively increasing complexity and end up creating a tighter coupling than needed -- instead of lowering complexity like we set out to achieve, we end up obtaining the opposite result.
+The above are clear-cut examples of cases when an abstraction greatly improves poor interfaces, but that's not always the end result. Abstractions can be a costly way of merging use cases when it's unclear whether those are naturally related in the first place. If we merge use cases too early, we might find that the complexity we're tucking away in an abstraction is quite small -- and thus offset by the abstraction's own complexity. If we merge cases which weren't all that related to begin with, we'd be effectively increasing complexity and end up creating a tighter coupling than needed -- instead of lowering complexity like we set out to achieve, we end up obtaining the opposite result.
 
 It is best to wait until a distinguishable pattern emerges and it becomes clear that introducing an abstraction would help keep complexity down. When such a pattern emerges, we can be confident that the use cases are indeed related, and we'll have better information about whether an abstraction would simplify our code.
 
diff --git a/chapters/ch04.asciidoc b/chapters/ch04.asciidoc
index d4b4ed2..d9c12ba 100644
--- a/chapters/ch04.asciidoc
+++ b/chapters/ch04.asciidoc
@@ -56,7 +56,7 @@ Express has middleware and routes, Angular has directives, services, and control
 
 Eventually, we'll come into requirements that don't exactly fit the mold proposed by our framework of choice. Generally, this means the required functionality belongs on a separate layer. For example, Express in Node.js is a framework concerned with handling HTTP requests and serving responses. If one of our API endpoints needs to result in an email being sent, we could embed email-sending logic in the controller for that API endpoint. However, if an API endpoint controller is already concerned with, say, publishing blog posts, then it would be hardly right to embed email-sending logic on that same controller, since it's a different concern entirely. Instead, what we could do is create a `subscribers` service component, with functionality such as `subscribe` which adds a subscriber after verifying their email, and `notify` which takes care of sending the emails. Taking this idea further still, perhaps most of the work in `subscribers.notify` should occur via yet another service component called `emails`, which takes care of properly configuring our email sending capability, and also has functionality to turn would-be emails into plain `console.log` statements for quick access to the contents of the emails during debug sessions.
 
-Having clearly defined layers is paramount to the design of effective and maintainable applications once we're past the prototyping stages. Layers can be made up of components which follow the conventions proposed by the frameworks we use, or they can be self-imposed like the service layer we discussed in the previous paragraph. Using layers, and as long as we favor function parameters over scope for context-passing, we can introduce horizonal scaling by placing several orthogonal components alongside each other, without letting them run into each others' concerns.
+Having clearly defined layers is paramount to the design of effective and maintainable applications once we're past the prototyping stages. Layers can be made up of components which follow the conventions proposed by the frameworks we use, or they can be self-imposed like the service layer we discussed in the previous paragraph. Using layers, and as long as we favor function parameters over scope for context-passing, we can introduce horizontal scaling by placing several orthogonal components alongside each other, without letting them run into each others' concerns.
 
 === 4.2 Refactoring Complex Code
 
@@ -108,7 +108,7 @@ We could've used more variables without creating a function, inlining the comput
 
 [source,javascript]
 ----
-const hasToken = auth === undefined || auth.token === undefined
+const hasToken = auth !== undefined && auth.token !== undefined
 const hasValidToken = hasToken && auth.expires > Date.now()
 if (hasValidToken) {
   return
@@ -143,7 +143,7 @@ if (response) {
 
 In the example, we're optimizing readability for the success case, while the failure handling is relegated to the very end of our piece of code. There's several problems with this approach. For one, we have to indulge in unnecessary nesting of every success condition, or otherwise put them all in a huge conditional statement. While it's rather easy to understand the success case, things can get tricky when we're trying to debug programs like this, as we need to keep the conditionals in our head the whole time we're reading the program.
 
-A better alternative is to flip the conditionals, placing all failure handling statements near the top. While counterintuitive at first, this approach has several benefits. It reduces nesting and eliminates `else` branches while promoting failure handling to the top of our code, and this has the added benefit that we'll become more aware of error handling and naturally gravitate towards thinking about the failure cases first, a great trait to have when doing application development, where forgetting to handle a failure case might result in an incosistent experience for end users with a hard-to-trace error on top.
+A better alternative is to flip the conditionals, placing all failure handling statements near the top. While counterintuitive at first, this approach has several benefits. It reduces nesting and eliminates `else` branches while promoting failure handling to the top of our code, and this has the added benefit that we'll become more aware of error handling and naturally gravitate towards thinking about the failure cases first, a great trait to have when doing application development, where forgetting to handle a failure case might result in an inconsistent experience for end users with a hard-to-trace error on top.
 
 [source,javascript]
 ----
@@ -162,7 +162,7 @@ Another benefit of guard clauses is almost implicit: given that they're placed n
 
 Guard clauses don't tell the reader everything they need to know that might go wrong when calling a function, but they give them a peek into expected immediate failure cases. Other things that might go wrong lie in the implementation details of the function. Perhaps we use a different service or library to fulfill the bulk of our function's task, and that service or library comes with its own set of nested guard clauses and potential failure cases that will bubble up all the way to our own function's outcome.
 
-==== 4.2.3 An Interdependency Piramid
+==== 4.2.3 An Interdependency Pyramid
 
 Writing straightforward code is not all that different from writing other straightforward texts. Texts are often arranged in paragraphs, which are somewhat comparable with functions: we can consider their input to be the reader's knowledge and everything else they've read so far in the text, and the output can be what the reader gets out of the paragraph.
 
@@ -237,7 +237,7 @@ function getUserModels(done) {
 }
 ----
 
-Now compare that code to the following bit of code, where we extracted the mapping function and shoved it out of the way. Given the mapping function doesn't need any of the scope from `getUserModels`, we can pull it out of that scope entirely, without the need to place `toUserModel` at the bottom of the `getUserModels` function. This means we can now also reuse `toUserModel` in other routines, we don't have to wonder whether the function actually depends on any of the contaning scope's context anymore, and `getUserModels` is now focused on the higher level flow where we find users, map them to their models, and return them.
+Now compare that code to the following bit of code, where we extracted the mapping function and shoved it out of the way. Given the mapping function doesn't need any of the scope from `getUserModels`, we can pull it out of that scope entirely, without the need to place `toUserModel` at the bottom of the `getUserModels` function. This means we can now also reuse `toUserModel` in other routines, we don't have to wonder whether the function actually depends on any of the containing scope's context anymore, and `getUserModels` is now focused on the higher level flow where we find users, map them to their models, and return them.
 
 [source,javascript]
 ----
@@ -478,7 +478,7 @@ A similar situation occurs when we have a concurrent flow that remains more or l
 
 In other cases, we might notice how there's a few different components that all need the same piece of functionality. Commenting features often fall in this case, where different components like user profiles, projects, or artifacts, might need the ability to receive, show, edit, and delete comments. This case can be interesting because it's the business requirement is not always identified upfront, and we might embed the child feature into the parent component before realizing it'd be useful to extract the feature so that it can be reused in other parent components. While this sounds obvious in hindsight, it's not always clear when we'll need to reuse some functionality somewhere else, and keeping every aspect of functionality isolated just in case we need to reuse them can be costly in terms of time and development effort.
 
-More often than not, however, abstractions can end up complicating matters. It might be that the tradeoff isn't worth it because the code becomes much harder to read, or maybe because the underlying code isn't mature enough yet, or we don't know what special requirements we may end up with for other objects adopting similar functionality, meaning we're not comfortable creating an abstraction that could lead to unforeseen problems in the future.
+More often than not, however, abstractions can end up complicating matters. It might be that the trade-off isn't worth it because the code becomes much harder to read, or maybe because the underlying code isn't mature enough yet, or we don't know what special requirements we may end up with for other objects adopting similar functionality, meaning we're not comfortable creating an abstraction that could lead to unforeseen problems in the future.
 
 Whenever we are uncertain about whether an abstraction is up to muster, it pays to go back to the original piece of code we had before introducing the abstraction, and comparing the two pieces. Is the new piece easier to understand, modify, and consume? Would that still be the case as a newcomer? Try and consider how the outcome to those questions would change if you hadn't looked at this code in a while. Ask your co-workers for their opinion, too; given they haven't seen that code yet and they may end up having to consume it, they're great candidates to help decide which approach is better.
 
@@ -510,15 +510,15 @@ At its heart, state is mutable. Even if the variable bindings themselves are imm
 
 Consider a game of chess, where each of two players starts with 16 pieces, each deterministically assigned a position on a checkerboard. The initial state is always the same. As each player inputs their actions, moving and trading pieces, the system state mutates. A few moves into the game, there is a good chance we'll be facing a game state we haven't ever experienced before. Computer program state is a lot like a game of chess, except there's more nuance in the way of user input, and an infinitude of possible board positions and state permutations.
 
-In the world of web development, a human decides to open a new tab in their favorite web browser and they then google for "cat in a pickle gifs". The browser allocates a new process through a system call to the operating system, which shifts some bits around on the physical hardware that lies inside the human’s computer. Before the HTTP request hits the network, we need to hit DNS servers, engaging in the elaborate process of casting `google.com` into an IP address. The browser then checks whether there’s a ServiceWorker installed, and assuming there isn’t one the request finally takes the default route of querying Google’s servers for the phrase “cat in a pickle gifs”.
+In the world of web development, a human decides to open a new tab in their favorite web browser and they then google for "cat in a pickle gifs". The browser allocates a new process through a system call to the operating system, which shifts some bits around on the physical hardware that lies inside the human’s computer. Before the HTTP request hits the network, we need to hit DNS servers, engaging in the elaborate process of casting `google.com` into an IP address. The browser then checks whether there's a ServiceWorker installed, and assuming there isn't one the request finally takes the default route of querying Google’s servers for the phrase “cat in a pickle gifs”.
 
 Naturally, Google receives this request at one of the front-end edges of its public network, in charge of balancing the load and routing requests to healthy back-end services. The query goes through a variety of analyzers that attempt to break it down to its semantic roots, stripping the query down to its essential keywords in an attempt to better match relevant results.
 
 The search engine figures out the 10 most relevant results for “cat pickle gif” out of billions of pages in its index – which was of course primed by a different system that’s also part of the whole – and at the same time, Google pulls down a highly targeted piece of relevant advertisement about cat gifs that matches what they believe is the demographic the human making the query belongs to, thanks to a sophisticated ad network that figures out whether the user is authenticated with Google through an HTTP header session cookie and the search results page starts being constructed and streamed to the human, who now appears impatient and fidgety.
 
-As the first few bits of HTML being streaming down the wire, the search engine produces its results and hands them back to the front-end servers, which includes it in the HTML stream that’s sent back to the human. The web browser has been working hard at this too, parsing the incomplete pieces of HTML that have been streaming down the wire as best it could, even daring to launch other admirably and equally-mind-boggling requests for HTTP resources presumed to be JavaScript, CSS, font, and image files as the HTML continues to stream down the wire. The first few chunks of HTML are converted into a DOM tree, and the browser would finally be able to begin rendering bits and pieces of the page on the screen, weren't it for the pending, equally-mind-boggling CSS and font requests.
+As the first few bits of HTML being streaming down the wire, the search engine produces its results and hands them back to the front-end servers, which includes it in the HTML stream that's sent back to the human. The web browser has been working hard at this too, parsing the incomplete pieces of HTML that have been streaming down the wire as best it could, even daring to launch other admirably and equally-mind-boggling requests for HTTP resources presumed to be JavaScript, CSS, font, and image files as the HTML continues to stream down the wire. The first few chunks of HTML are converted into a DOM tree, and the browser would finally be able to begin rendering bits and pieces of the page on the screen, weren't it for the pending, equally-mind-boggling CSS and font requests.
 
-As the CSS stylesheets and fonts are transmitted, the browser begins modeling the CSSOM and getting a more complete picture of how to turn the HTML and CSS plain text chunks provided by Google servers into a graphical representation that the human finds pleasant. Browser extensions get a chance to meddle with the content, removing the highly targeted piece of relevant advertisement about cat gifs before I even realize Google hoped I wouldn’t block ads this time around.
+As the CSS stylesheets and fonts are transmitted, the browser begins modeling the CSSOM and getting a more complete picture of how to turn the HTML and CSS plain text chunks provided by Google servers into a graphical representation that the human finds pleasant. Browser extensions get a chance to meddle with the content, removing the highly targeted piece of relevant advertisement about cat gifs before I even realize Google hoped I wouldn't block ads this time around.
 
 A few seconds have passed by since I first decided to search for cat in a pickle gifs. Needless to say, thousands of others brought similarly inane requests. To the same systems. During this time.
 
@@ -526,13 +526,13 @@ Not only does this example demonstrate the marvelous machinery and infrastructur
 
 ==== 4.3.2 Eliminating Incidental State
 
-We've established that the overall state of a system has little to do with our ability to comprehend parts of that same system. Our focus in reducing state-based entropy must then lie in the individual aspects of the system. It’s for this reason that breaking apart large pieces of code is so effective. We’re reducing the amount of state local to each given aspect of the system, and that’s the kind of state that’s worth taking care of, since it’s what we can keep in our heads and make sense of.
+We've established that the overall state of a system has little to do with our ability to comprehend parts of that same system. Our focus in reducing state-based entropy must then lie in the individual aspects of the system. It's for this reason that breaking apart large pieces of code is so effective. We’re reducing the amount of state local to each given aspect of the system, and that's the kind of state that's worth taking care of, since it's what we can keep in our heads and make sense of.
 
-Whenever there's persistance involved, there's going to be a discrepancy between ephemeral state and realized state. In the case of a web application, we could define ephemeral state as any user input that hasn't resulted in state being persisted yet, as might be the case of an unsaved user preference that might be lost unless persisted. We can say realized state is the state that has been persisted, and that different programs might have different strategies on how to convert ephemeral state into realized state. A web application might adopt an Offline-First pattern where ephemeral state is automatically synchronized to an IndexedDB database in the browser, and eventually realized by updating the state persisted on a back-end system. When the Offline-First page is reloaded, unrealized state may be pushed to the back-end or discarded.
+Whenever there's persistence involved, there's going to be a discrepancy between ephemeral state and realized state. In the case of a web application, we could define ephemeral state as any user input that hasn't resulted in state being persisted yet, as might be the case of an unsaved user preference that might be lost unless persisted. We can say realized state is the state that has been persisted, and that different programs might have different strategies on how to convert ephemeral state into realized state. A web application might adopt an Offline-First pattern where ephemeral state is automatically synchronized to an IndexedDB database in the browser, and eventually realized by updating the state persisted on a back-end system. When the Offline-First page is reloaded, unrealized state may be pushed to the back-end or discarded.
 
 Incidental state can occur when we have a piece of data that's used in several parts of an application, and which is derived from other pieces of data. When the original piece of data is updated, it wouldn't be hard to inadvertently leave the derived pieces of data in their current state, making them stale when compared to the updated original pieces of data. As an example, consider a piece of user input in Markdown and the HTML representation derived from that piece of Markdown. If the piece of Markdown is updated but the previously compiled pieces of HTML are not, then different parts of the system might display different bits of HTML out of what was apparently the same single Markdown source.
 
-When we persist derived state, we're putting the original and the derived data at risk of falling out of sync. This isn't the case just when dealing with persistance layers, but can also occur in a few other scenarios as well. When dealing with caching layers, their content may become stale because the underlying original piece of content is updated but we forget to invalidate pieces of content derived from the updated data. Database denormalization is another common occurrence of this problem, whereby creating derived state can result in synchronization problems and stale byproducts of the original data.
+When we persist derived state, we're putting the original and the derived data at risk of falling out of sync. This isn't the case just when dealing with persistence layers, but can also occur in a few other scenarios as well. When dealing with caching layers, their content may become stale because the underlying original piece of content is updated but we forget to invalidate pieces of content derived from the updated data. Database denormalization is another common occurrence of this problem, whereby creating derived state can result in synchronization problems and stale byproducts of the original data.
 
 This lack of synchronization is often observed in discussion forum software, where user profiles are denormalized into comment objects in an effort to save a database roundtrip. When users update later update their profile, however, their old comments preserve an stale avatar, signature, or display name. To avoid this kind of issue, we should always consider recomputing derived state from its roots. Even though doing so won't always be possible, performant, or even practical, encouraging this kind of thinking across a development team will, if anything, increase awareness about the subtle intricacies of denormalized state.
 
@@ -542,7 +542,7 @@ As long as we're aware of the risks of data denormalization, we can then indulge
 
 State is inevitable. As we discussed in section 4.3.1, though, the full picture hardly affects our ability to maintain small parts of that state tree. In the local case -- each of the interrelated but ultimately separate pieces of code we work with in our day to day -- all that matters are the inputs we receive and the outputs we produce. That said, generating a large amount of output where we could instead emit a single piece of information is undesirable.
 
-When all intermediate state is contained inside a component instead of being leaked to others, we're reducing the friction in interacting with our component or function. The more we condense state into its smallest possible representation for output purposes, the better contained our functions will become. Incidentaly, we're making the interface easier to consume. Since there's less state to draw from, there's fewer ways of consuming that state. This reduces the amount of possible use cases, but by favoring composability over serving every possible need, we're making each piece of functionality, when evaluated on its own, simpler.
+When all intermediate state is contained inside a component instead of being leaked to others, we're reducing the friction in interacting with our component or function. The more we condense state into its smallest possible representation for output purposes, the better contained our functions will become. Incidentally, we're making the interface easier to consume. Since there's less state to draw from, there's fewer ways of consuming that state. This reduces the amount of possible use cases, but by favoring composability over serving every possible need, we're making each piece of functionality, when evaluated on its own, simpler.
 
 One other case where we may incidentally increase complexity is whenever we modify the property values of an input. This type of operation should be made extremely explicit, as to not be confused, and avoided where possible. If we assume functions to be defined as the equation between the inputs we receive and the outputs we produce, then the side-effects  are ill-advised. Mutations on the input within the body of a function is one example of side-effects, which can be a source of bugs and confusion, particularly due to the difficulty in tracking down the source for these mutations.
 
@@ -703,7 +703,7 @@ console.log(multiply(add(5, 3), 2)) // <- 16
 
 Taking this concept beyond basic mathematics, we can begin to see how this decoupling of form and function, or state and logic, can be increasingly beneficial. It's easier to serialize plain data over the wire, keep it consistent across different environments, and make it interoperable regardless of the logic, than if we tightly coupled data and the logic around it.
 
-Functions are, to a certain degree, hopelessly coupled to the data they receive as inputs: in order for the function to work as expected, the data it receives must satisfy its contract for that piece of input. Within the bounds of a function's proper execution, the data must have a certain shape, traits, or adhere to whatever restrictions the function has in place. These restrictions may be somewhat lax (e.g "must have a `toString` method"), highly specific (e.g "must be a function that accepts 3 arguments and returns a decimal number between 0 and 1"), or anywhere in between. A simple interface is usually highly restrictive (e.g accepting only a boolean value). Meanwhile, it's not uncommon for loose interfaces to become burdened by their own flexibility, leading to complex implementations that attempt to accomodate many different shapes and sizes of the same input parameter.
+Functions are, to a certain degree, hopelessly coupled to the data they receive as inputs: in order for the function to work as expected, the data it receives must satisfy its contract for that piece of input. Within the bounds of a function's proper execution, the data must have a certain shape, traits, or adhere to whatever restrictions the function has in place. These restrictions may be somewhat lax (e.g. "must have a `toString` method"), highly specific (e.g. "must be a function that accepts 3 arguments and returns a decimal number between 0 and 1"), or anywhere in between. A simple interface is usually highly restrictive (e.g. accepting only a boolean value). Meanwhile, it's not uncommon for loose interfaces to become burdened by their own flexibility, leading to complex implementations that attempt to accommodate many different shapes and sizes of the same input parameter.
 
 We should aim to keep logic restrictive and only as flexible as deemed necessary by business requirements. When an interface starts out being restrictive we can always slowly open it up later as new use cases and requirements arise, but by starting out with a small use case we're able to grow the interface into something that's naturally better fit to handle specific, real-world use cases.
 
