diff --git a/src/problem1/main.js b/src/problem1/main.js
new file mode 100644
index 0000000000..91bd268074
--- /dev/null
+++ b/src/problem1/main.js
@@ -0,0 +1,29 @@
+var sum_to_n_a = function (n) {
+ if (n < 1) {
+ return 0
+ }
+
+ let sum = 0;
+ for (let i = 1; i <= n; i++) {
+ sum += i;
+ }
+ return sum
+};
+
+var sum_to_n_b = function (n) {
+ if (n < 1) {
+ return 0
+ }
+ return n + sum_to_n_b(n - 1)
+};
+
+var sum_to_n_c = function (n) {
+ if (n < 1) {
+ return 0
+ }
+ return n * (n + 1) / 2
+};
+
+console.log(sum_to_n_a(5))
+console.log(sum_to_n_b(5))
+console.log(sum_to_n_c(5))
diff --git a/src/problem2/index.html b/src/problem2/index.html
index 4058a68bff..58fcecc9e4 100644
--- a/src/problem2/index.html
+++ b/src/problem2/index.html
@@ -1,27 +1,136 @@
-
-
+
+
-
- Fancy Form
-
-
+
+
+ Swap
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Swap
+
Exchange tokens instantly
+
-
+
+
+
+ You pay
+
+
+
+
+
+
+
+
+
+
-
-
+
+
+
+
-
-
-
diff --git a/src/problem2/script.js b/src/problem2/script.js
index e69de29bb2..8d1ecdf755 100644
--- a/src/problem2/script.js
+++ b/src/problem2/script.js
@@ -0,0 +1,248 @@
+const PRICES_URL = "https://interview.switcheo.com/prices.json";
+const ICON_BASE = "https://raw.githubusercontent.com/Switcheo/token-icons/main/tokens";
+const icon = (s) => `${ICON_BASE}/${s}.svg`;
+const $ = (id) => document.getElementById(id);
+
+let prices = {}, tokens = [], fromToken = null, toToken = null;
+
+const els = {
+ fromBtn: $("from-btn"),
+ fromChevron: $("from-chevron"),
+ fromIcon: $("from-icon"),
+ fromSymbol: $("from-symbol"),
+ fromDropdown: $("from-dropdown"),
+ fromList: $("from-list"),
+ fromSearch: $("from-search"),
+ toBtn: $("to-btn"),
+ toChevron: $("to-chevron"),
+ toIcon: $("to-icon"),
+ toSymbol: $("to-symbol"),
+ toDropdown: $("to-dropdown"),
+ toList: $("to-list"),
+ toSearch: $("to-search"),
+ inputAmount: $("input-amount"),
+ outputAmount: $("output-amount"),
+ rateBar: $("rate-bar"),
+ amountError: $("amount-error"),
+ globalError: $("global-error"),
+ confirmBtn: $("confirm-btn"),
+ btnText: $("btn-text"),
+ spinner: $("spinner"),
+ swapDirBtn: $("swap-direction"),
+ toast: $("toast"),
+ toastMsg: $("toast-msg"),
+ themeToggle: $("theme-toggle"),
+ iconSun: $("icon-sun"),
+ iconMoon: $("icon-moon"),
+};
+
+// ── Init ──────────────────────────────────────────────────────────────────────
+async function init() {
+ try {
+ const data = await fetch(PRICES_URL).then(r => r.json());
+ data.forEach(({ currency, price }) => { prices[currency] = parseFloat(price); });
+ tokens = Object.keys(prices).sort();
+
+ renderList(els.fromList, selectFrom);
+ renderList(els.toList, selectTo);
+
+ const preferred = ["ETH", "BTC", "USDC", "SWTH"];
+ const first = preferred.find(t => tokens.includes(t)) || tokens[0];
+ const second = preferred.filter(t => t !== first).find(t => tokens.includes(t)) || tokens[1];
+ selectFrom(first);
+ selectTo(second);
+ } catch {
+ showErr(els.globalError, "⚠ Failed to load prices. Please refresh.");
+ }
+}
+
+// ── Render token list ─────────────────────────────────────────────────────────
+function renderList(ul, onSelect, list = tokens) {
+ ul.innerHTML = list.map(t => `
+
+
+ ${t}
+
+ $${prices[t].toFixed(prices[t] < 0.01 ? 6 : 4)}
+
+ `).join("");
+
+ ul.querySelectorAll("li").forEach(li =>
+ li.addEventListener("click", () => onSelect(li.dataset.symbol))
+ );
+}
+
+function markActive(ul, symbol) {
+ ul.querySelectorAll("li").forEach(li =>
+ li.classList.toggle("active-token", li.dataset.symbol === symbol)
+ );
+}
+
+// ── Token selection ───────────────────────────────────────────────────────────
+function selectToken(side, symbol) {
+ if (side === "from") fromToken = symbol;
+ else toToken = symbol;
+
+ els[`${side}Symbol`].textContent = symbol;
+ els[`${side}Icon`].src = icon(symbol);
+ els[`${side}Icon`].style.visibility = "";
+ closeDropdown(els[`${side}Dropdown`], els[`${side}Chevron`]);
+ markActive(els[`${side}List`], symbol);
+ compute();
+ updateRate();
+}
+
+const selectFrom = (s) => selectToken("from", s);
+const selectTo = (s) => selectToken("to", s);
+
+// ── Compute & rate ────────────────────────────────────────────────────────────
+function compute() {
+ const val = parseFloat(els.inputAmount.value);
+ const canCompute = fromToken && toToken && !isNaN(val) && val > 0;
+ els.outputAmount.value = canCompute
+ ? fmt((val * prices[fromToken]) / prices[toToken])
+ : "";
+}
+
+function updateRate() {
+ if (!fromToken || !toToken) {
+ els.rateBar.style.display = "none";
+ return;
+ }
+ const rate = prices[fromToken] / prices[toToken];
+ els.rateBar.innerHTML = `
+
+ 1 ${fromToken} ≈
+ ${fmt(rate)} ${toToken}
+ live prices`;
+ els.rateBar.style.display = "flex";
+}
+
+function fmt(n) {
+ if (!n) return "0";
+ if (n < 0.000001) return n.toExponential(4);
+ if (n < 0.01) return n.toFixed(8);
+ if (n < 1000) return n.toFixed(6);
+ return n.toLocaleString("en-US", { maximumFractionDigits: 4 });
+}
+
+// ── Dropdown ──────────────────────────────────────────────────────────────────
+function openDropdown(d, c) {
+ d.classList.remove("hidden");
+ c.classList.add("rotate-180");
+}
+function closeDropdown(d, c) {
+ d.classList.add("hidden");
+ c.classList.remove("rotate-180");
+}
+function closeAll() {
+ closeDropdown(els.fromDropdown, els.fromChevron);
+ closeDropdown(els.toDropdown, els.toChevron);
+}
+
+els.fromBtn.addEventListener("click", e => {
+ e.stopPropagation();
+ const wasHidden = els.fromDropdown.classList.contains("hidden");
+ closeAll();
+ if (wasHidden) {
+ openDropdown(els.fromDropdown, els.fromChevron);
+ els.fromSearch.focus();
+}
+});
+
+els.toBtn.addEventListener("click", e => {
+ e.stopPropagation();
+ const wasHidden = els.toDropdown.classList.contains("hidden");
+ closeAll();
+ if (wasHidden) {
+ openDropdown(els.toDropdown, els.toChevron);
+ els.toSearch.focus();
+}
+});
+
+document.addEventListener("click", closeAll);
+els.fromDropdown.addEventListener("click", e => e.stopPropagation());
+els.toDropdown.addEventListener("click", e => e.stopPropagation());
+
+els.fromSearch.addEventListener("input", e => {
+ const q = e.target.value.toLowerCase();
+ renderList(els.fromList, selectFrom, tokens.filter(t => t.toLowerCase().includes(q)));
+ markActive(els.fromList, fromToken);
+});
+els.toSearch.addEventListener("input", e => {
+ const q = e.target.value.toLowerCase();
+ renderList(els.toList, selectTo, tokens.filter(t => t.toLowerCase().includes(q)));
+ markActive(els.toList, toToken);
+});
+
+// ── Errors ────────────────────────────────────────────────────────────────────
+const showErr = (el, msg) => {
+ el.textContent = msg; el.classList.remove("hidden");
+};
+const clearErr = (el) => {
+ el.textContent = ""; el.classList.add("hidden");
+};
+
+els.inputAmount.addEventListener("input", () => {
+ clearErr(els.amountError);
+ compute();
+});
+
+// ── Swap direction ────────────────────────────────────────────────────────────
+els.swapDirBtn.addEventListener("click", () => {
+ if (!fromToken || !toToken) return;
+ const [a, b, out] = [fromToken, toToken, els.outputAmount.value];
+ selectFrom(b);
+ selectTo(a);
+ if (out) { els.inputAmount.value = parseFloat(out.replace(/,/g, "")); compute(); }
+});
+
+// ── Submit ────────────────────────────────────────────────────────────────────
+els.confirmBtn.addEventListener("click", () => {
+ clearErr(els.amountError);
+ clearErr(els.globalError);
+
+ const val = parseFloat(els.inputAmount.value);
+ if (!fromToken || !toToken)
+ return showErr(els.globalError, "Please select both tokens.");
+ if (fromToken === toToken)
+ return showErr(els.globalError, "Cannot swap a token for itself.");
+ if (!els.inputAmount.value || isNaN(val) || val <= 0) {
+ showErr(els.amountError, "Enter a valid amount greater than 0.");
+ return els.inputAmount.focus();
+ }
+
+ els.confirmBtn.disabled = true;
+ els.spinner.classList.remove("hidden");
+ els.btnText.textContent = "Processing…";
+
+ setTimeout(() => {
+ const msg = `Swapped ${fmt(val)} ${fromToken} → ${els.outputAmount.value} ${toToken}`;
+ els.confirmBtn.disabled = false;
+ els.spinner.classList.add("hidden");
+ els.btnText.textContent = "CONFIRM SWAP";
+ els.inputAmount.value = els.outputAmount.value = "";
+ showToast(msg);
+ }, 1800);
+});
+
+// ── Theme ────────────────────────────────────────────────────────────────────
+els.themeToggle.addEventListener("click", () => {
+ const isDark = document.documentElement.classList.toggle("dark");
+ els.iconSun.classList.toggle("hidden", !isDark);
+ els.iconMoon.classList.toggle("hidden", isDark);
+});
+
+// ── Toast ─────────────────────────────────────────────────────────────────────
+function showToast(msg) {
+ els.toastMsg.textContent = msg;
+ els.toast.classList.replace("opacity-0", "opacity-100");
+ els.toast.classList.replace("translate-y-20", "translate-y-0");
+ setTimeout(() => {
+ els.toast.classList.replace("opacity-100", "opacity-0");
+ els.toast.classList.replace("translate-y-0", "translate-y-20");
+ }, 3500);
+}
+
+init();
diff --git a/src/problem2/style.css b/src/problem2/style.css
index 915af91c72..08e33c08c2 100644
--- a/src/problem2/style.css
+++ b/src/problem2/style.css
@@ -1,8 +1,25 @@
-body {
- display: flex;
- flex-direction: row;
- align-items: center;
- justify-content: center;
- min-width: 360px;
- font-family: Arial, Helvetica, sans-serif;
+/* Remove number input spinners */
+input[type=number]::-webkit-outer-spin-button,
+input[type=number]::-webkit-inner-spin-button { -webkit-appearance: none; }
+input[type=number] { -moz-appearance: textfield; }
+
+/* Custom scrollbar for token dropdowns */
+.dropdown-scroll::-webkit-scrollbar { width: 4px; }
+.dropdown-scroll::-webkit-scrollbar-track { background: transparent; }
+.dropdown-scroll::-webkit-scrollbar-thumb { background: rgba(0,0,0,.15); border-radius: 2px; }
+.dark .dropdown-scroll::-webkit-scrollbar-thumb { background: rgba(255,255,255,.15); }
+
+/* Active token in dropdown */
+li.active-token { background-color: rgba(99,102,241,.12); }
+
+/* Disabled confirm button */
+.confirm-btn:disabled {
+ background: #e2e8f0;
+ cursor: not-allowed;
+ color: #94a3b8;
}
+.dark .confirm-btn:disabled {
+ background: #1e293b;
+ color: #475569;
+}
+
diff --git a/src/problem3/ANALYSIS.md b/src/problem3/ANALYSIS.md
new file mode 100644
index 0000000000..5ee5c26076
--- /dev/null
+++ b/src/problem3/ANALYSIS.md
@@ -0,0 +1,95 @@
+# Problem 3 — Code Review: `WalletPage`
+
+## Issues & Fixes
+
+| # | Issue | Fix |
+|---|-------|-----|
+| 1 | `lhsPriority` used but never declared → `ReferenceError` | Rename to `balancePriority` |
+| 2 | Filter keeps `amount <= 0` (inverted logic) | Change to `amount > 0` |
+| 3 | `getPriority` re-created on every render | Move outside the component |
+| 4 | `blockchain: any` — no type safety | Use a `Blockchain` union type |
+| 5 | `prices` in `useMemo` deps but unused inside it | Remove from dependency array |
+| 6 | `formattedBalances` computed but never used (dead code) | Merge into the same `useMemo` chain |
+| 7 | `rows` maps `sortedBalances` but accesses `.formatted` (doesn't exist) | Map the formatted array instead |
+| 8 | `key={index}` on a sorted/filtered list | Use `key={balance.currency}` |
+| 9 | `sort` comparator returns `undefined` when priorities are equal | Return `0` explicitly (or use subtraction) |
+| 10 | `children` destructured from props but never rendered | Remove from destructuring |
+| 11 | `filter` → `sort` → `map` = 3 array passes + 2 intermediate allocations | Combine `filter` + `map` into one `reduce` → 2 passes total |
+
+## Note: `useMemo` and referential stability of `balances`
+
+`useMemo` uses `Object.is` (reference equality) to compare dependencies. Since `balances` is an array, the memo only skips recomputation if `useWalletBalances()` returns the **same array reference** between renders.
+
+If the hook returns a new array every render, `useMemo` recomputes every render — making it useless. To guard against this:
+
+```tsx
+// Stabilize the reference if useWalletBalances() is not internally memoized
+const stableBalances = useMemo(() => balances, [JSON.stringify(balances)]);
+```
+
+> The ideal fix is ensuring `useWalletBalances` returns a stable reference internally.
+
+## Refactored Version
+
+```tsx
+type Blockchain = 'Osmosis' | 'Ethereum' | 'Arbitrum' | 'Zilliqa' | 'Neo';
+
+interface WalletBalance {
+ currency: string;
+ amount: number;
+ blockchain: Blockchain;
+}
+
+interface FormattedWalletBalance extends WalletBalance {
+ formatted: string;
+}
+
+// O(1) lookup table — moved outside component, no re-creation on render
+const BLOCKCHAIN_PRIORITY: Record = {
+ Osmosis: 100,
+ Ethereum: 50,
+ Arbitrum: 30,
+ Zilliqa: 20,
+ Neo: 20,
+};
+
+const getPriority = (blockchain: Blockchain): number =>
+ BLOCKCHAIN_PRIORITY[blockchain] ?? -99;
+
+interface Props extends BoxProps {}
+
+const WalletPage: React.FC = (props: Props) => {
+ const { ...rest } = props;
+ const balances = useWalletBalances();
+ const prices = usePrices();
+
+ // reduce: filter + format in one O(n) pass, then sort O(n log n)
+ const sortedAndFormattedBalances = useMemo((): FormattedWalletBalance[] => {
+ const formatted = balances.reduce((acc, balance) => {
+ if (getPriority(balance.blockchain) > -99 && balance.amount > 0) {
+ acc.push({ ...balance, formatted: balance.amount.toFixed() });
+ }
+ return acc;
+ }, []);
+
+ return formatted.sort((lhs, rhs) =>
+ getPriority(rhs.blockchain) - getPriority(lhs.blockchain)
+ );
+ }, [balances]); // prices intentionally excluded — not used in this memo
+
+ const rows = sortedAndFormattedBalances.map((balance: FormattedWalletBalance) => {
+ const usdValue = prices[balance.currency] * balance.amount;
+ return (
+
+ );
+ });
+
+ return {rows}
;
+};
+```
diff --git a/src/problem3/main.tsx b/src/problem3/main.tsx
new file mode 100644
index 0000000000..d8dfada6dd
--- /dev/null
+++ b/src/problem3/main.tsx
@@ -0,0 +1,59 @@
+type Blockchain = 'Osmosis' | 'Ethereum' | 'Arbitrum' | 'Zilliqa' | 'Neo';
+
+interface WalletBalance {
+ currency: string;
+ amount: number;
+ blockchain: Blockchain;
+}
+
+interface FormattedWalletBalance extends WalletBalance {
+ formatted: string;
+}
+
+const BLOCKCHAIN_PRIORITY: Record = {
+ Osmosis: 100,
+ Ethereum: 50,
+ Arbitrum: 30,
+ Zilliqa: 20,
+ Neo: 20,
+};
+
+const getPriority = (blockchain: Blockchain): number =>
+ BLOCKCHAIN_PRIORITY[blockchain] ?? -99;
+
+const WalletPage: React.FC = (props: BoxProps) => {
+ const { ...rest } = props;
+ const balances = useWalletBalances();
+ const prices = usePrices();
+
+ const sortedAndFormattedBalances = useMemo((): FormattedWalletBalance[] => {
+ // Single reduce pass: filter + format combined → O(n), then sort → O(n log n)
+ const formatted = balances.reduce((acc: FormattedWalletBalance[], balance: WalletBalance) => {
+ if (getPriority(balance.blockchain) > -99 && balance.amount > 0) {
+ acc.push({ ...balance, formatted: balance.amount.toFixed() });
+ }
+ return acc;
+ }, []);
+
+ return formatted.sort((lhs: WalletBalance, rhs: WalletBalance) =>
+ getPriority(rhs.blockchain) - getPriority(lhs.blockchain)
+ );
+ }, [balances]);
+
+ const rows = sortedAndFormattedBalances.map((balance: FormattedWalletBalance) => {
+ const usdValue = prices[balance.currency] * balance.amount;
+ return (
+
+ );
+ });
+
+ return {rows}
;
+};
+
+export default WalletPage;
\ No newline at end of file