-
Section 1: Getting Started
-
Section 2: Core Syntax and Types
-
Section 3: Strings and Numbers in Depth
-
Section 4: Control Flow
-
Section 5: Functions
-
Section 6: Objects and Arrays
-
Section 7: Maps, Sets, and Symbols
-
Section 8: Asynchronous JavaScript
-
Section 9: Object-Oriented and Prototypes
-
Section 10: The DOM
-
Section 11: Browser APIs
-
Section 12: Modern JavaScript (ES2015-ES2025)
-
Section 13: Functional Programming Patterns
-
Section 14: Error Handling and Debugging
-
Section 15: Testing
-
Section 16: Accessibility for JavaScript Developers
-
Section 17: Internationalization and Localization
-
Section 18: Performance
-
Section 19: Node.js Fundamentals
-
Section 20: Regular Expressions
-
Section 21: Design Patterns in JavaScript
-
Section 22: Security Basics
-
Section 23: Data Structures and Algorithms in JavaScript
-
Section 24: Practical Projects
-
Section 25: More Advanced Async Patterns
-
Section 26: More Object and Class Practice
-
Section 27: Working with Dates and Internationalization
-
Section 28: Web Components
-
Section 29: More DOM and Browser Practice
-
Section 30: Build Tooling for Vanilla JavaScript
-
Section 31: More Practice Projects
-
Section 32: Interview and Algorithm Practice
-
Section 33: Error Objects (MDN Reference)
-
Section 34: TypedArrays and Binary Data
-
Section 35: Reflection and Metaprogramming (MDN Reference)
-
Section 36: More Global Functions (MDN Reference)
202: Practice Exercise: Building a Simple State Machine
I want to show you a piece of code I encountered in a code review a few months ago. A junior dev was building a simple order processing system. They used a string to track the order status and a function to update it. On the surface, it looked clean, but it had a massive logical hole.
let orderStatus = 'pending';
function updateStatus(newStatus) {
console.log(`Changing status from ${orderStatus} to ${newStatus}`);
orderStatus = newStatus;
}
// This works fine
updateStatus('paid');
// This also works fine
updateStatus('shipped');
// Oops. Someone calls this by mistake or via a weird API edge case:
updateStatus('pending');
console.log(orderStatus); // 'pending'
The Problem with "Open" State Transitions
The bug here isn't a syntax error; it's a logic error. In a real-world application, an order that has already been 'shipped' should never be able to jump back to 'pending'. If that happens, you've got a nightmare on your hands—refunds might trigger twice, or shipping labels might be regenerated for an item already on a truck.
The problem is that the state is "open." The updateStatus function blindly trusts whatever input it gets. When your application grows to ten or twenty possible states, trying to manage these rules with a giant if/else block inside your update function becomes a maintenance disaster. You'll end up with "spaghetti logic" where you're constantly adding checks like if (status !== 'shipped' && status !== 'delivered').
Implementing a Transition Map
The professional way to handle this is to move the logic out of the function and into a configuration object—a state machine. Instead of asking "What is the new state?", we ask "Is this transition allowed from the current state?"
const OrderMachine = {
initial: 'pending',
states: {
pending: {
PAY: 'paid',
CANCEL: 'cancelled'
},
paid: {
SHIP: 'shipped',
REFUND: 'pending'
},
shipped: {
DELIVER: 'delivered'
},
delivered: {
// No outgoing transitions; this is a final state
},
cancelled: {
// No outgoing transitions
}
}
};
let currentState = OrderMachine.initial;
function transition(action) {
const possibleTransitions = OrderMachine.states[currentState];
const nextState = possibleTransitions ? possibleTransitions[action] : null;
if (nextState) {
console.log(`Transitioning from ${currentState} via ${action} to ${nextState}`);
currentState = nextState;
} else {
console.error(`Invalid action ${action} for current state ${currentState}`);
}
}
transition('PAY'); // Success: pending -> paid
transition('SHIP'); // Success: paid -> shipped
transition('PAY'); // Error: Invalid action PAY for current state shipped
I prefer this approach because the OrderMachine object acts as a single source of truth. If the business requirements change—say, we now allow 'shipped' orders to be 'cancelled'—I don't have to hunt through my functions for if statements. I just add CANCEL: 'cancelled' to the shipped state object. It makes the code self-documenting and nearly impossible to put into an illegal state.
📋 Practical Task
Build a Media Player State Controller
Your task is to build a state machine that manages a music player. The player can be in one of four states: stopped, playing, paused, and buffering.
Implement a transition map and a handleAction function that enforces the following rules:
- From stopped, you can only
play(moves tobuffering). - From buffering, you can only
resolve(moves toplaying) orstop(moves tostopped). - From playing, you can
pause(moves topaused) orstop(moves tostopped). - From paused, you can
resume(moves toplaying) orstop(moves tostopped).
Test your machine by attempting a sequence of valid moves, and then attempt an invalid move (like trying to resume while the state is stopped) to ensure your error handling catches it.
There are no comments for now.