{
  "assessmentTests": {
    "javascript_test": {
      "name": "JavaScript Test",
      "desc": "30 scenario questions on core syntax and coercion, functions/scope/closures and this, objects/arrays and data structures, and the event loop/async/prototype idioms — find out whether your JavaScript matches what a job posting means by JavaScript proficiency.",
      "recommendation": "Your JavaScript skills profile",
      "results": {
        "beginner": {
          "name": "Beginner",
          "desc": "You can write working functions and objects, but the questions you missed cluster around type coercion and reference semantics rather than syntax — == quietly converting types before comparing, an array assignment that shares one object rather than copying it, why NaN is never equal to itself. None of this is about being bad at JavaScript; these are the specific rules that trip up people who learned the language by writing code that happened to work rather than by learning how values and references actually behave. They matter at work because each one is where code runs without an error and still does the wrong thing.",
          "recommendation": "Start with three things, in this order: why == coerces types before comparing while === does not, why assigning one array or object to a new variable copies the reference rather than the value, and how var is hoisted and initialized to undefined while let and const stay in the temporal dead zone until their declaration runs. MDN's JavaScript guide covers all three with runnable examples."
        },
        "intermediate": {
          "name": "Intermediate",
          "desc": "You handle everyday application code comfortably — functions, array methods, straightforward control flow — and would not be slowed down by routine feature work. The gap between here and Advanced is mostly in what happens when JavaScript's scoping and timing rules interact: a var captured inside a loop's callbacks all reading the same final value, a this that silently stops pointing at the object once a method is detached from it, a spread copy that looks deep but only copies one level. Those are the kind of bugs that pass a quick read-through and only show up under a specific runtime condition.",
          "recommendation": "Focus on how scope and this actually resolve at call time rather than definition time: why a var-based for loop hands every callback the same final counter value while let does not, how this is determined by how a function is called rather than where it is written, and why a spread or Object.assign only copies the top level of an object. Then the event loop, since that is what trips up people who already know promises individually."
        },
        "advanced": {
          "name": "Advanced",
          "desc": "This is the level most job postings mean by \"strong JavaScript.\" You read a chain of filter/map/reduce and can predict exactly what it returns without mutating the original array, you know why a synchronous console.log always beats a microtask which always beats a setTimeout, and you reach for Promise.all knowing it rejects the instant any one promise does rather than waiting for the rest. What separates this band from the top is the prototype and object-model side of the work: how the prototype chain resolves an inherited method versus hasOwnProperty, and what a private class field actually hides.",
          "recommendation": "Push into the parts that protect state other code also touches: how JavaScript's prototype chain resolves method lookups that aren't on the instance itself, what a class's private #fields actually hide from outside access, and how JSON.stringify treats undefined and functions differently inside a plain object versus inside an array. MDN's material on the event loop and the prototype chain is the natural next stop for both."
        },
        "expert": {
          "name": "Expert",
          "desc": "You scored at the top of every section — core syntax, types and coercion; functions, scope, closures and this; objects, arrays and data structures; and async, the event loop, prototypes and idioms. Practically, that means you can be handed a stranger's function and explain why it returns what it returns, not just what the syntax says it should do, which is the harder and more valuable skill. At this level the language itself is rarely the limiting factor; the limit is usually async control flow or the shape of the data underneath it.",
          "recommendation": "The returns are now in design and diagnosis: reasoning about a race condition between Promise.all and Promise.race rather than reciting their definitions, choosing when a closure is the right tool for private state versus when a class's private field is, and reading a microtask-vs-macrotask ordering bug in production logs rather than in a quiz. If you are being screened for a role, describe a timing bug you found — a stale closure variable or an out-of-order async callback — rather than naming JavaScript features; it demonstrates the reasoning, not just the vocabulary."
        }
      },
      "questions": [
        {
          "question": "console.log(x);\nvar x = 5;\nWhat does this print?",
          "options": [
            {
              "icon": "",
              "label": "5 — var declarations run immediately, before any other code"
            },
            {
              "icon": "",
              "label": "ReferenceError: x is not defined"
            },
            {
              "icon": "",
              "label": "undefined — var declarations are hoisted to the top of their scope and initialized to undefined, but the assignment executes in place, so before it runs the variable exists but holds undefined"
            },
            {
              "icon": "",
              "label": "null"
            }
          ]
        },
        {
          "question": "console.log(y);\nlet y = 5;\nWhat happens?",
          "options": [
            {
              "icon": "",
              "label": "It throws ReferenceError: Cannot access 'y' before initialization — let is hoisted to the top of its scope but stays in the temporal dead zone until the declaration line actually executes"
            },
            {
              "icon": "",
              "label": "It prints undefined, the same as var would"
            },
            {
              "icon": "",
              "label": "It prints 5"
            },
            {
              "icon": "",
              "label": "It throws a SyntaxError at parse time"
            }
          ]
        },
        {
          "question": "const arr = [1, 2, 3];\narr.push(4);\nconsole.log(arr.length);\nWhat does this print?",
          "options": [
            {
              "icon": "",
              "label": "4 — const only prevents reassigning the arr binding itself, not mutating the array object it points to, so push still works"
            },
            {
              "icon": "",
              "label": "It throws a TypeError, because arr is const"
            },
            {
              "icon": "",
              "label": "3 — push is blocked on a const array"
            },
            {
              "icon": "",
              "label": "undefined"
            }
          ]
        },
        {
          "question": "console.log('' == 0);\nconsole.log('' === 0);\nWhat do the two lines print?",
          "options": [
            {
              "icon": "",
              "label": "true then true"
            },
            {
              "icon": "",
              "label": "false then false"
            },
            {
              "icon": "",
              "label": "true then false — == coerces the empty string to 0 before comparing, while === compares type and value with no coercion, and a string is never strictly equal to a number"
            },
            {
              "icon": "",
              "label": "false then true"
            }
          ]
        },
        {
          "question": "console.log(NaN === NaN);\nconsole.log(Number.isNaN(NaN));\nWhat do the two lines print?",
          "options": [
            {
              "icon": "",
              "label": "true then true"
            },
            {
              "icon": "",
              "label": "true then false"
            },
            {
              "icon": "",
              "label": "false then false"
            },
            {
              "icon": "",
              "label": "false then true — NaN is the only value in JavaScript that is not equal to itself under ===, but Number.isNaN() correctly detects it anyway"
            }
          ]
        },
        {
          "question": "console.log(typeof null);\nWhat does this print?",
          "options": [
            {
              "icon": "",
              "label": "\"null\""
            },
            {
              "icon": "",
              "label": "\"object\" — a long-standing bug in the language: typeof null returns \"object\" even though null is a primitive, and it has been kept for backwards compatibility ever since"
            },
            {
              "icon": "",
              "label": "\"undefined\""
            },
            {
              "icon": "",
              "label": "\"boolean\""
            }
          ]
        },
        {
          "question": "Which of the following is truthy when used in an if condition?",
          "options": [
            {
              "icon": "",
              "label": "0"
            },
            {
              "icon": "",
              "label": "\"\" (an empty string)"
            },
            {
              "icon": "",
              "label": "null"
            },
            {
              "icon": "",
              "label": "\"0\" — a non-empty string is always truthy in JavaScript, even when its content is itself the string \"0\""
            }
          ]
        },
        {
          "question": "foo();\nfunction foo() { console.log('a'); }\nbar();\nvar bar = function() { console.log('b'); };\nWhat happens?",
          "options": [
            {
              "icon": "",
              "label": "Both calls print, \"a\" then \"b\""
            },
            {
              "icon": "",
              "label": "foo() prints \"a\"; bar() throws TypeError: bar is not a function — function declarations are fully hoisted with their body, but a var-assigned function expression only hoists the var itself (as undefined), not the assignment"
            },
            {
              "icon": "",
              "label": "Both calls throw ReferenceError"
            },
            {
              "icon": "",
              "label": "foo() throws; bar() prints \"b\""
            }
          ]
        },
        {
          "question": "function makeCounter() {\n  let count = 0;\n  return function() { count++; return count; };\n}\nconst counter = makeCounter();\nconsole.log(counter());\nconsole.log(counter());\nWhat do the two lines print?",
          "options": [
            {
              "icon": "",
              "label": "1 then 1"
            },
            {
              "icon": "",
              "label": "0 then 1"
            },
            {
              "icon": "",
              "label": "undefined then undefined"
            },
            {
              "icon": "",
              "label": "1 then 2 — the returned function closes over the same count variable from makeCounter's scope, and that variable persists between calls rather than resetting"
            }
          ]
        },
        {
          "question": "for (var i = 0; i < 3; i++) {\n  setTimeout(() => console.log(i), 0);\n}\nWhat does this print?",
          "options": [
            {
              "icon": "",
              "label": "0, 1, 2"
            },
            {
              "icon": "",
              "label": "3, 3, 3 — var is function-scoped, not block-scoped, so all three callbacks close over the same i, which has already finished the loop and equals 3 by the time any of them actually runs"
            },
            {
              "icon": "",
              "label": "0, 0, 0"
            },
            {
              "icon": "",
              "label": "undefined, undefined, undefined"
            }
          ]
        },
        {
          "question": "for (let i = 0; i < 3; i++) {\n  setTimeout(() => console.log(i), 0);\n}\nWhat does this print?",
          "options": [
            {
              "icon": "",
              "label": "3, 3, 3"
            },
            {
              "icon": "",
              "label": "0, 0, 0"
            },
            {
              "icon": "",
              "label": "undefined, undefined, undefined"
            },
            {
              "icon": "",
              "label": "0, 1, 2 — let creates a fresh binding of i for each loop iteration, so each callback closes over its own separate copy rather than one shared variable"
            }
          ]
        },
        {
          "question": "function greet() { return `Hi, ${this.name}`; }\nconst person = { name: 'Ana' };\nconsole.log(greet.call(person));\nWhat does this print?",
          "options": [
            {
              "icon": "",
              "label": "\"Hi, Ana\" — Function.prototype.call invokes the function with this explicitly set to its first argument, so this.name resolves against person"
            },
            {
              "icon": "",
              "label": "\"Hi, undefined\" — call() only works on functions that are already methods of an object"
            },
            {
              "icon": "",
              "label": "It throws a TypeError, because greet is not a method of person"
            },
            {
              "icon": "",
              "label": "\"Hi, ${this.name}\" — call() does not evaluate the template literal"
            }
          ]
        },
        {
          "question": "const timer = {\n  seconds: 0,\n  start: function() {\n    setInterval(() => { this.seconds++; }, 1000);\n  },\n};\nWhy do people prefer the arrow function over function() { this.seconds++ } inside setInterval here?",
          "options": [
            {
              "icon": "",
              "label": "Arrow functions don't have their own this — they capture this lexically from the enclosing start function, which is timer (since start was called as timer.start()), so this.seconds correctly refers to timer.seconds on every tick"
            },
            {
              "icon": "",
              "label": "Arrow functions execute faster than regular functions at runtime"
            },
            {
              "icon": "",
              "label": "Arrow functions automatically bind this to the global object, which happens to equal timer"
            },
            {
              "icon": "",
              "label": "Arrow functions convert this into a global variable automatically"
            }
          ]
        },
        {
          "question": "function multiply(a, b) { return a * b; }\nconst double = multiply.bind(null, 2);\nconsole.log(double(5));\nWhat does this print?",
          "options": [
            {
              "icon": "",
              "label": "It throws an error, because bind requires both arguments up front"
            },
            {
              "icon": "",
              "label": "10 — bind returns a new function with this and any leading arguments pre-set; calling double(5) supplies the remaining argument b=5, so multiply(2, 5) runs"
            },
            {
              "icon": "",
              "label": "NaN"
            },
            {
              "icon": "",
              "label": "7, because bind adds its arguments to the call rather than substituting them"
            }
          ]
        },
        {
          "question": "function outer() {\n  let x = 10;\n  function inner() {\n    let x = 20;\n    console.log(x);\n  }\n  inner();\n  console.log(x);\n}\nouter();\nWhat do the two console.log calls print?",
          "options": [
            {
              "icon": "",
              "label": "20 then 20"
            },
            {
              "icon": "",
              "label": "20 then 10 — inner's own let x shadows outer's x only inside inner's scope; outer's own x is never touched"
            },
            {
              "icon": "",
              "label": "10 then 10"
            },
            {
              "icon": "",
              "label": "It throws a ReferenceError, because x is declared twice"
            }
          ]
        },
        {
          "question": "const a = [1, 2, 3];\nconst b = a;\nb.push(4);\nconsole.log(a.length);\nWhat does this print?",
          "options": [
            {
              "icon": "",
              "label": "3 — b is a separate copy of a"
            },
            {
              "icon": "",
              "label": "undefined"
            },
            {
              "icon": "",
              "label": "It throws a TypeError, because a is const"
            },
            {
              "icon": "",
              "label": "4 — arrays are objects, so const b = a copies the reference, not the array; mutating b through push also mutates the exact same array a points to"
            }
          ]
        },
        {
          "question": "const original = { name: 'Ana', tags: ['x', 'y'] };\nconst copy = { ...original };\ncopy.name = 'Bea';\ncopy.tags.push('z');\nconsole.log(original.name, original.tags.length);\nWhat does this print?",
          "options": [
            {
              "icon": "",
              "label": "'Ana' 2 — spread deep-clones every nested object and array"
            },
            {
              "icon": "",
              "label": "'Ana' 3 — spread is only a shallow copy: a primitive property like name is copied by value, so reassigning copy.name never touches original, but a nested array like tags is copied by reference, so pushing into copy.tags mutates the exact same array original.tags points to"
            },
            {
              "icon": "",
              "label": "'Bea' 3 — spread copies everything by reference, so both the top-level and nested changes carry back to original"
            },
            {
              "icon": "",
              "label": "'Bea' 2"
            }
          ]
        },
        {
          "question": "const { a: x = 10, b: y = 20 } = { a: 5 };\nconsole.log(x, y);\nWhat does this print?",
          "options": [
            {
              "icon": "",
              "label": "undefined undefined"
            },
            {
              "icon": "",
              "label": "5 undefined"
            },
            {
              "icon": "",
              "label": "10 20"
            },
            {
              "icon": "",
              "label": "5 20 — a is renamed to x and is present, so it keeps its value 5; b is renamed to y and is absent, so it falls back to its default 20"
            }
          ]
        },
        {
          "question": "const [first, ...rest] = [1, 2, 3, 4];\nconsole.log(rest);\nWhat does this print?",
          "options": [
            {
              "icon": "",
              "label": "[1, 2, 3, 4]"
            },
            {
              "icon": "",
              "label": "[2, 3, 4] — rest gathers every remaining element after first into a brand new array"
            },
            {
              "icon": "",
              "label": "2"
            },
            {
              "icon": "",
              "label": "[1]"
            }
          ]
        },
        {
          "question": "const nums = [10, 1, 2];\nnums.sort();\nconsole.log(nums);\nWhat does this print?",
          "options": [
            {
              "icon": "",
              "label": "[1, 2, 10] — sort always orders numbers numerically in ascending order by default"
            },
            {
              "icon": "",
              "label": "[10, 1, 2] — sort does not mutate the original array"
            },
            {
              "icon": "",
              "label": "[1, 10, 2] — Array.prototype.sort's default comparator converts elements to strings and compares them lexicographically, so \"10\" sorts before \"2\"; sort also mutates the array in place rather than returning a new one"
            },
            {
              "icon": "",
              "label": "[2, 1, 10]"
            }
          ]
        },
        {
          "question": "const user = { profile: { age: 0 } };\nconsole.log(user.profile?.age ?? 'unknown');\nconsole.log(user.address?.city ?? 'unknown');\nWhat do the two lines print?",
          "options": [
            {
              "icon": "",
              "label": "'unknown' then 'unknown' — ?? treats 0 as missing, the same way || does"
            },
            {
              "icon": "",
              "label": "0 then 'unknown' — ?? only falls back on null or undefined, not on other falsy values like 0, so age (0) is kept as-is; user.address is undefined, so ?. short-circuits the whole chain to undefined and ?? then supplies 'unknown'"
            },
            {
              "icon": "",
              "label": "0 then undefined"
            },
            {
              "icon": "",
              "label": "'unknown' then 0"
            }
          ]
        },
        {
          "question": "const obj = { a: undefined, b: function(){}, c: NaN, d: [undefined, function(){}, 1] };\nconsole.log(JSON.stringify(obj));\nWhat does this print?",
          "options": [
            {
              "icon": "",
              "label": "'{\"a\":null,\"b\":null,\"c\":null,\"d\":[null,null,1]}' — every unsupported value becomes null everywhere, in objects and arrays alike"
            },
            {
              "icon": "",
              "label": "'{\"a\":undefined,\"b\":undefined,\"c\":NaN,\"d\":[undefined,null,1]}'"
            },
            {
              "icon": "",
              "label": "'{\"c\":null,\"d\":[null,null,1]}' — object properties whose value is undefined or a function are omitted entirely, and NaN serializes to null, but inside an array those same unsupported values become null instead of being dropped, because removing an array element would shift every later index"
            },
            {
              "icon": "",
              "label": "It throws a TypeError, because NaN cannot be serialized to JSON"
            }
          ]
        },
        {
          "question": "console.log('1');\nsetTimeout(() => console.log('2'), 0);\nPromise.resolve().then(() => console.log('3'));\nconsole.log('4');\nWhat order do these print in?",
          "options": [
            {
              "icon": "",
              "label": "1, 2, 3, 4"
            },
            {
              "icon": "",
              "label": "1, 4, 2, 3"
            },
            {
              "icon": "",
              "label": "1, 4, 3, 2 — synchronous code runs first (so 1 and 4 print immediately), then the microtask queue (Promise callbacks) drains completely before the event loop moves on to the next macrotask, so 3 prints before the setTimeout callback's 2"
            },
            {
              "icon": "",
              "label": "1, 3, 4, 2"
            }
          ]
        },
        {
          "question": "async function foo() {\n  console.log('a');\n  await null;\n  console.log('b');\n}\nconsole.log('start');\nfoo();\nconsole.log('end');\nWhat order do these print in?",
          "options": [
            {
              "icon": "",
              "label": "start, a, end, b — foo() runs synchronously up to its first await, so 'a' logs immediately when foo() is called; the await then suspends foo and returns control to the caller, so 'end' logs before the resumed 'b', which runs later as a microtask"
            },
            {
              "icon": "",
              "label": "start, a, b, end"
            },
            {
              "icon": "",
              "label": "start, end, a, b"
            },
            {
              "icon": "",
              "label": "a, start, end, b"
            }
          ]
        },
        {
          "question": "Promise.all([\n  Promise.resolve(1),\n  Promise.reject('err'),\n  Promise.resolve(3),\n]).then(r => console.log('ok', r)).catch(e => console.log('fail', e));\nWhat does this print?",
          "options": [
            {
              "icon": "",
              "label": "'fail err' — Promise.all rejects as soon as any one of its input promises rejects, ignoring the results of the others, so the catch handler runs with that rejection's reason"
            },
            {
              "icon": "",
              "label": "'ok' [1, 3]"
            },
            {
              "icon": "",
              "label": "'ok' [1, undefined, 3]"
            },
            {
              "icon": "",
              "label": "It throws synchronously and no handler ever runs"
            }
          ]
        },
        {
          "question": "const fast = new Promise(res => setTimeout(() => res('fast'), 10));\nconst slow = new Promise(res => setTimeout(() => res('slow'), 100));\nPromise.race([fast, slow]).then(v => console.log(v));\nWhat does this print?",
          "options": [
            {
              "icon": "",
              "label": "'slow'"
            },
            {
              "icon": "",
              "label": "['fast', 'slow']"
            },
            {
              "icon": "",
              "label": "'fast' — Promise.race settles as soon as the first of its input promises settles, and the 10ms timer resolves well before the 100ms one"
            },
            {
              "icon": "",
              "label": "It throws an error, because race requires exactly two promises"
            }
          ]
        },
        {
          "question": "function Animal(name) { this.name = name; }\nAnimal.prototype.speak = function() { return `${this.name} makes a sound`; };\nconst dog = new Animal('Rex');\nconsole.log(dog.speak());\nconsole.log(dog.hasOwnProperty('speak'));\nWhat do the two lines print?",
          "options": [
            {
              "icon": "",
              "label": "'Rex makes a sound' then true"
            },
            {
              "icon": "",
              "label": "undefined then false"
            },
            {
              "icon": "",
              "label": "It throws a TypeError, because dog has no speak method of its own"
            },
            {
              "icon": "",
              "label": "'Rex makes a sound' then false — speak is defined on Animal.prototype, not on the dog instance itself; dog finds it through the prototype chain when it's called, but hasOwnProperty only checks the instance's own properties, not anything inherited"
            }
          ]
        },
        {
          "question": "class Counter {\n  #count = 0;\n  increment() { this.#count++; return this.#count; }\n}\nconst c = new Counter();\nconsole.log(c.increment());\nconsole.log(c.count);\nWhat do the two lines print?",
          "options": [
            {
              "icon": "",
              "label": "1 then undefined — #count is a private class field, readable and writable only from inside the class's own methods; c.count (without the #) is a completely different, ordinary property that was never set, so it is undefined"
            },
            {
              "icon": "",
              "label": "1 then 0"
            },
            {
              "icon": "",
              "label": "1 then 1"
            },
            {
              "icon": "",
              "label": "It throws a SyntaxError when accessing c.count"
            }
          ]
        },
        {
          "question": "const items = ['a', 'b'];\nconst label = `Items: ${items.length > 0 ? items.join(', ') : 'none'}`;\nconsole.log(label);\nWhat does this print?",
          "options": [
            {
              "icon": "",
              "label": "'Items: ${items.length > 0 ? items.join(\\', \\') : \\'none\\'}'"
            },
            {
              "icon": "",
              "label": "'Items: none'"
            },
            {
              "icon": "",
              "label": "'Items: a, b' — template literals evaluate any expression placed inside ${...}, including a full ternary, and interpolate its result directly into the string"
            },
            {
              "icon": "",
              "label": "It throws a SyntaxError, because ternaries aren't allowed inside template literals"
            }
          ]
        },
        {
          "question": "const nums = [1, 2, 3, 4, 5];\nconst result = nums.filter(n => n % 2 === 0).map(n => n * 10).reduce((sum, n) => sum + n, 0);\nconsole.log(result, nums);\nWhat does this print?",
          "options": [
            {
              "icon": "",
              "label": "60 [1, 2, 3, 4, 5] — filter, map and reduce each return a new array or value without mutating the original array, so nums is unchanged while result is the final reduced sum (2 and 4, doubled to 20 and 40, summed to 60)"
            },
            {
              "icon": "",
              "label": "60 [2, 4, 20, 40]"
            },
            {
              "icon": "",
              "label": "150 [1, 2, 3, 4, 5]"
            },
            {
              "icon": "",
              "label": "60 [20, 40]"
            }
          ]
        }
      ],
      "optionOrderVersion": "50fc0eba818f2d72"
    }
  }
}
