< BACK
DEV.CARDS
▸
■ JAVASCRIPT
00000001VARIABLES & TYPES
BASICSTYPES
let x = 10; // reassignable const y = 20; // constant — prefer this // var z; ← avoid! (function-scoped) let name = 'Alice'; // string let age = 25; // number (int+float same type) let ok = true; // boolean let empty = null; // intentionally empty let undef; // undefined (never assigned) let big = 9007n; // BigInt let sym = Symbol('id'); // unique typeof 'hi' // 'string' typeof 42 // 'number' typeof null // 'object' ← historic JS bug!
■ VARIABLES & TYPES
what is a variable?
📦 A variable is a labelled box. You put a value inside and give the box a name so you can find it later.
let age = 25 means "create a box called age and put 25 inside it." Anywhere you write age later in the code, JS opens that box and uses what's inside.const vs let vs var
Use
Use
const by default — it seals the box. Anyone reading your code knows the value won't be replaced.Use
let only when you genuinely need to reassign — loop counters, accumulating totals, state toggles.⚠ Never use var. It was the original keyword before 2015. It ignores block scope — a variable declared inside an
if with var leaks outside the curly braces, causing subtle bugs. Just forget it exists.the 7 primitive types
string— text in quotes. Backtick strings let you embed expressions:`Hello ${name}`number— any number. JS doesn't separate integers from decimals — both are just numberboolean— onlytrueorfalse. Like a light switchnull— intentionally empty. "This box is empty on purpose"undefined— JS assigned this automatically; you never gave it a valueSymbol— guaranteed-unique identifier. Rare in everyday codeBigInt— numbers too large for regularnumber. Addnsuffix:9007n
⚠ typeof null === 'object' — This is a famous bug from 1995 that was never fixed because it would break millions of existing websites. Just memorise this quirk.
00000010FUNCTIONS
BASICSARROWREST
function greet(name) { return `Hello ${name}`; } // Arrow — shorter, inherits outer `this` const add = (a, b) => a + b; const square = n => n * n; const hi = () => 'hi'; // Default parameter const welcome = (name = 'World') => `Welcome, ${name}`; // Rest — collects remaining args into array const sum = (...nums) => nums.reduce((a, b) => a + b, 0); sum(1, 2, 3); // → 6
■ FUNCTIONS
what is a function?
🏭 A function is a reusable recipe. You write the steps once, name them, then "call" (run) the recipe as many times as you want with different ingredients (inputs).
The values you pass when calling are arguments. The names in the definition are parameters. greet("Alice") runs the greet recipe with Alice, handing back "Hello Alice!"return is the output — what the function hands back.declaration vs arrow
Declarations are hoisted — JS moves them to the top before running, so you can call them before you wrote them. Occasionally handy.
Arrow functions are the modern choice. Two key differences:
Arrow functions are the modern choice. Two key differences:
- Shorter — great for one-liners
- No own
this— they inherit it from the enclosing scope, making them safe in class callbacks
💡 Use arrow functions for almost everything. Use declarations for top-level named functions only.
template literals
Backtick strings with
${} are template literals. The ${} slot accepts any JavaScript expression — variable, math, ternary, function call. Vastly cleaner than string concatenation: `Hello ${name}!` vs "Hello " + name + "!".default & rest parameters
Default:
Rest (
name = "World" provides a fallback when the caller passes nothing (or passes undefined).Rest (
...nums): collects all remaining arguments into a real array. Useful when you don't know in advance how many values someone will pass.00000011ARRAYS
MAPFILTERREDUCE
const arr = [1, 2, 3, 4]; // Mutating (changes original): arr.push(5) // add end → [1,2,3,4,5] arr.pop() // remove last arr.unshift(0) // add front arr.splice(1,1) // remove 1 item at index 1 arr.sort((a,b)=>a-b) // numeric sort — always pass fn! // Non-mutating (returns new array): arr.map(x => x * 2) // [2,4,6,8] arr.filter(x => x > 2) // [3,4] arr.slice(1, 3) // [2,3] // Returns a single value: arr.reduce((acc,x)=>acc+x, 0) // 10 arr.find(x => x > 2) // 3 arr.includes(2) // true arr.some(x => x > 3) // true arr.every(x => x > 0) // true
■ ARRAYS
what is an array?
📋 An array is an ordered, numbered list. Index 0 = item #1, index 1 = item #2.
arr[0] = first item. Arrays can hold anything — numbers, strings, objects, even other arrays — all mixed together.the big three: map, filter, reduce
map() — Transform every item and get a new list. Like saying "for each item, apply this recipe." The original is untouched, the result has the same length.filter() — Keep only items where your test function returns true. Like crossing out things that don't qualify. Result may be shorter.reduce() — Collapse all items into one value. Add up numbers, build an object from an array. The second argument is the starting value (use 0 for sums).💡 All three never mutate the original — they return new arrays. Chain them:
arr.filter(x => x > 0).map(x => x * 2)the sort() gotcha
⚠ sort() without a comparator is alphabetical — even for numbers!
[10, 2, 30].sort() gives [10, 2, 30] because "1" comes before "2" alphabetically. Always pass (a, b) => a - b for ascending numeric sort. Forgetting this is a rite of passage.quick reference
find(fn)— first matching item (orundefined)findIndex(fn)— index of first match (or-1)includes(x)— does x exist? → booleansome(fn)— at least one match?every(fn)— all items match?flat()— flatten one level of nestingslice(1, 3)— copy from index 1 up to (not including) 3
00000100OBJECTS & DESTRUCTURING
OBJECTSSPREADDESTRUCT
const user = { name: 'Alice', age: 25 }; user.name; // dot access user['age']; // bracket (dynamic key) // Destructuring — unpack multiple at once const { name, age } = user; const { name: n, age = 18 } = user; // ↑ rename ↑ default // Spread — copy + override (immutable update) const updated = { ...user, age: 26 }; // Shorthand — when key = variable name const city = 'Jakarta'; const obj = { name, city }; // same as name:name Object.keys(user) // ['name','age'] Object.values(user) // ['Alice',25] Object.entries(user) // [['name','Alice'],['age',25]]
■ OBJECTS & DESTRUCTURING
what is an object?
🗂️ An object is a form with labelled fields. An ID card has fields: name, age, role. An object is exactly that — a collection of named values. Each name is a "key," its content is a "value." While arrays use numbers as index (
Use dot notation when the key name is known ahead of time. Use bracket notation arr[0]), objects use string names (user.name).user["age"] when the key is stored in a variable.destructuring — the time-saver
Without destructuring:
With destructuring (one line):
Rename:
Default:
Works on arrays too:
const name = user.name;const age = user.age;With destructuring (one line):
const { name, age } = user;Rename:
{ name: n } = "pull out name, call it n locally."Default:
{ age = 18 } = "use 18 if the field doesn't exist or is undefined."Works on arrays too:
const [a, b, ...rest] = [1, 2, 3, 4];spread — the copy machine
{ ...user, age: 26 } = "copy everything from user, then override age with 26." Creates a brand new object — the original is untouched.This pattern is everywhere in React for immutable state updates:
{ ...oldState, loading: false }⚠ Spread is shallow! Nested objects are copied by reference, not cloned. If
user.address is an object, both the copy and original share the same address object. Change one, you change both.Object utilities
Object.keys(), Object.values(), and Object.entries() let you loop over objects. They convert an object's parts into arrays, which you can then chain with .map(), .filter(), etc. entries() is especially powerful — it gives you [key, value] pairs perfect for rebuilding a modified object.00000101ASYNC / AWAIT & PROMISES
ASYNCPROMISESFETCH
async function getUser(id) { try { const res = await fetch(`/api/users/${id}`); if (!res.ok) throw new Error(res.status); return await res.json(); } catch (err) { console.error('Failed:', err); } } // Parallel requests — much faster! const [users, posts] = await Promise.all([ fetch('/api/users').then(r => r.json()), fetch('/api/posts').then(r => r.json()), ]); // allSettled — gets results even if some fail const results = await Promise.allSettled([p1, p2]); // Custom delay const delay = ms => new Promise( res => setTimeout(res, ms)); await delay(1000); // pause 1s
■ ASYNC / AWAIT & PROMISES
why does async/await exist?
☕ JavaScript is like a single-threaded barista. When you order coffee (a network request), the barista doesn't freeze staring at the machine. They take your order, go serve others, and come back when the coffee is ready. That "come back when ready" mechanic is what Promises handle.
Without async/await, you'd write messy async/await is syntactic sugar that makes this look like normal sequential code..then().then().catch() chains ("callback hell"). async/await reads top-to-bottom like synchronous code — just with await before anything that takes time.step-by-step breakdown
1. Mark the function
2. Put
3. Wrap in
async. Now it can use await.2. Put
await before any Promise (like fetch()). This pauses that function until the Promise resolves — JS keeps running other things meanwhile.3. Wrap in
try/catch. Any thrown error (or rejected Promise) is caught here.⚠ fetch() does NOT throw on HTTP errors! A 404 or 500 response still "succeeds" as far as fetch is concerned — it just sets
res.ok = false. You MUST manually check if (!res.ok) throw new Error(...). Every beginner gets burned by this at least once.sequential vs parallel
Two
awaits in a row run one after another — each waits for the previous to finish. If each takes 500ms, total is 1000ms.Promise.all([p1, p2]) fires both simultaneously and waits for whichever is slower. Total: ~500ms. Always use Promise.all when requests are independent of each other.⚠ If ONE promise in Promise.all fails, the whole thing rejects. Use
Promise.allSettled when you want results from all of them regardless of failures — it gives you {status, value|reason} for each.00000110CONTROL FLOW
LOGICLOOPSOPERATORS
// Ternary — inline if/else const label = age >= 18 ? 'adult' : 'minor'; // ?? — fallback for null/undefined ONLY const val = input ?? 'default'; // 0, '', false still pass through! // ?. — safe property access chain const city = user?.address?.city; const r = fn?.(); // call only if fn exists // Loops for (const item of arr) {} // values for (const key in obj) {} // keys arr.forEach((v, i) => {}) // can't break early // Short-circuit isAdmin && showPanel(); // right runs only if left truthy val || 'fallback'; // right if left is ANY falsy val ?? 'fallback'; // right only if null/undefined
■ CONTROL FLOW
ternary — compact if/else
condition ? valueIfTrue : valueIfFalseRead it as "if age is 18+, give me 'adult', otherwise 'minor'." Use ternary for simple one-liners. For complex multi-branch logic, regular
if/else is more readable. ⚠ Never nest ternaries inside ternaries. It becomes unreadable instantly.
?? vs || — a crucial distinction
Both provide a fallback value, but they differ on what triggers it:
|| triggers for any falsy: null, undefined, 0, "", false, NaN?? triggers only for null or undefined⚠ Real bug: A user sets their score to 0. With
score || 100, you display 100 instead of 0 because 0 is falsy. With score ?? 100, you correctly show 0. When in doubt, ?? is almost always what you actually mean.optional chaining ?.
Normally,
user.address.city throws a TypeError if address is null. The whole script crashes.user?.address?.city means: "try to access address — if it's null/undefined, give me undefined instead of crashing." Each ?. is a safety guard. Essential when working with API responses where fields might be missing.loops — which to use
for...of→ gives you values. Use for arrays.for...in→ gives you keys (property names). Use for objects.forEach→ like for...of but as a method. Cannotbreakorreturnearly.- Classic
for (let i=0; i<n; i++)→ when you need the index number.
00000111CLASSES, CLOSURES & MODULES
OOPCLOSURESMODULES
class Animal { #sound = '...'; // private field static count = 0; // class-level constructor(name) { this.name = name; Animal.count++; } speak() { return this.#sound; } } class Dog extends Animal { speak() { return 'Woof!'; } } // Closure — function remembers its birthplace function makeCounter() { let n = 0; return () => ++n; // closes over n } const c = makeCounter(); c(); // 1 c(); // 2 // ES Modules export const PI = 3.14; // named export default function main() {} // default import main, { PI } from './mod';
■ CLASSES, CLOSURES & MODULES
classes — blueprints for objects
🏗️ A class is a cookie cutter.
new Dog("Rex") uses the Dog cutter to stamp out a specific Dog instance. The constructor is the setup instructions that run when stamping.#sound (private): the # prefix means this property can only be accessed inside the class. Complete encapsulation.static count: belongs to the class itself, not any instance. All instances share one copy.extends: Dog inherits everything from Animal automatically and can override specific methods.closures — a superpower
A closure is a function that remembers the variables from where it was created — even after the outer function has returned and is "done."
In
In
makeCounter: the inner function closes over n. Even after makeCounter returns, calling c() still increments that same n. The variable lives on inside the closure.💡 Closures are everywhere: React useState, event handlers, setTimeout callbacks, factory functions that remember configuration. Understanding closures is what separates beginners from intermediate developers.
ES modules — splitting code into files
- Named export:
export const PI = 3.14— many per file. Import with exact name in{} - Default export:
export default function main()— one per file. Import with any name you choose - Import all:
import * as utils from "./utils"— gives you an object with all named exports
00001000ERROR HANDLING
ERRORSTRY/CATCH
try { const d = JSON.parse(badStr); } catch (err) { if (err instanceof SyntaxError) { console.error('Bad JSON'); } else throw err; // re-throw unknown! } finally { // always runs — cleanup here } // Custom error class class AppError extends Error { constructor(msg, statusCode = 500) { super(msg); this.statusCode = statusCode; this.name = 'AppError'; } } throw new AppError('Not found', 404); // Safe JSON parse helper const safeJSON = (str, fallback = null) => { try { return JSON.parse(str); } catch { return fallback; } };
■ ERROR HANDLING
what happens without error handling?
💥 A chef with no plan B: if an ingredient is missing, the whole kitchen stops. That's an unhandled error in JS — the entire script crashes.
try/catch gives the kitchen a plan B.try / catch / finally breakdown
try { } — put risky code here. Any code that might throw.catch (err) { } — runs only if something in try throws. The err object has:err.message— human-readable descriptionerr.name— the error type: "TypeError", "SyntaxError", etc.err.stack— full stack trace showing where it came from
finally { } — runs regardless of success or failure. Use it for cleanup: close DB connections, hide loading spinners, release file handles.⚠ Always re-throw errors you don't recognise. Silently swallowing unknown errors hides real bugs. Only handle what you understand; re-throw the rest.
custom error classes
Plain
Always set
Error only has a message. By extending it, you attach extra metadata — like an HTTP status code. Now your error handler can check err.statusCode and send the right HTTP response automatically.Always set
this.name in custom error classes so instanceof checks work correctly after bundling.