Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 31 additions & 7 deletions readme.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,34 @@
# 99Tech Code Challenge #1 #
## Solutions (Frontend Engineer application)

Note that if you fork this repository, your responses may be publicly linked to this repo.
Please submit your application along with the solutions attached or linked.
Applying for the **Frontend Engineer** role, so Problems 1–3 were attempted;
Problems 4–5 are left as-is (out of scope for this role).

It is important that you minimally attempt the problems, even if you do not arrive at a working solution.
### [Problem 1 — Three ways to sum to n](src/problem1/sum_to_n.js)

## Submission ##
You can either provide a link to an online repository, attach the solution in your application, or whichever method you prefer.
We're cool as long as we can view your solution without any pain.
Three implementations (iterative loop, closed-form Gauss formula, recursive),
plus a `runTestCases()` function that checks all three against a shared set
of inputs, including large values where the recursive version is
intentionally skipped (it would overflow the call stack).

```bash
node src/problem1/sum_to_n.js
```

### [Problem 2 — Fancy Form](src/problem2/)

Currency swap form built with React + TypeScript + Vite. Uses live prices
from `interview.switcheo.com/prices.json` and token icons from
[Switcheo/token-icons](https://github.com/Switcheo/token-icons). See
[src/problem2/README.md](src/problem2/README.md) for details and how it's
structured.

```bash
cd src/problem2
npm install
npm run dev
```

### [Problem 3 — Messy React](src/problem3/analysis.md)

Write-up of the inefficiencies/anti-patterns found in the given code block,
plus a refactored version with an explanation of each change.
Empty file removed src/problem1/.keep
Empty file.
82 changes: 82 additions & 0 deletions src/problem1/sum_to_n.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/**
* Problem 1: Three ways to sum to n
*
* Input: n - any integer
* Assumption: the result is always < Number.MAX_SAFE_INTEGER.
* Output: sum of integers from 1 to n, e.g. sum_to_n(5) === 1+2+3+4+5 === 15.
*
* These implementations assume n >= 0, consistent with the "sum to n" spec
* (there is no natural summation from 1 to a negative n).
*/

// A. Iterative loop — O(n) time, O(1) space.
// Most straightforward and easy to read; fine for small/medium n.
var sum_to_n_a = function (n) {
let sum = 0;
for (let i = 1; i <= n; i++) {
sum += i;
}
return sum;
};

// B. Mathematical formula (Gauss' sum) — O(1) time, O(1) space.
// The fastest option, no loop needed: n * (n + 1) / 2.
var sum_to_n_b = function (n) {
return (n * (n + 1)) / 2;
};

// C. Recursive — O(n) time, O(n) space (call stack).
// Demonstrates a functional/recursive approach. Not suitable for very large n
// (risks a stack overflow), but included as a distinct technique from A and B.
var sum_to_n_c = function (n) {
if (n <= 0) return 0;
return n + sum_to_n_c(n - 1);
};

// Runs a shared set of test cases through all three implementations and
// checks that they agree, so a regression in any one of them is obvious.
//
// sum_to_n_c is recursive, so it's only exercised for n small enough to fit
// the call stack; sum_to_n_a/b (loop/formula) are also checked against much
// larger n, where a naive recursive approach would overflow the stack.
function runTestCases() {
const recursionSafeCases = [0, 1, 5, 10, 100, 1000];
const largeOnlyCases = [1_000_000, 12345678];
let allPassed = true;

for (const n of recursionSafeCases) {
const a = sum_to_n_a(n);
const b = sum_to_n_b(n);
const c = sum_to_n_c(n);
const expected = (n * (n + 1)) / 2;
const passed = a === expected && b === expected && c === expected;
allPassed = allPassed && passed;

console.log(
`sum_to_n(${n}) => a=${a} b=${b} c=${c} expected=${expected} ${
passed ? "PASS" : "FAIL"
}`
);
}

for (const n of largeOnlyCases) {
const a = sum_to_n_a(n);
const b = sum_to_n_b(n);
const expected = (n * (n + 1)) / 2;
const passed = a === expected && b === expected;
allPassed = allPassed && passed;

console.log(
`sum_to_n(${n}) => a=${a} b=${b} expected=${expected} (c skipped: would overflow the call stack) ${
passed ? "PASS" : "FAIL"
}`
);
}

console.log(allPassed ? "\nAll test cases passed." : "\nSome test cases failed.");
return allPassed;
}

runTestCases();

module.exports = { sum_to_n_a, sum_to_n_b, sum_to_n_c, runTestCases };
24 changes: 24 additions & 0 deletions src/problem2/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
*.local

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
8 changes: 8 additions & 0 deletions src/problem2/.oxlintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"plugins": ["react", "typescript", "oxc"],
"rules": {
"react/rules-of-hooks": "error",
"react/only-export-components": ["warn", { "allowConstantExport": true }]
}
}
47 changes: 47 additions & 0 deletions src/problem2/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Fancy Form — Currency Swap

A currency swap form built with **React + TypeScript + Vite**.

## Run it

```bash
npm install
npm run dev
```

## What it does

- Fetches live prices from `https://interview.switcheo.com/prices.json` on load.
The feed has duplicate/stale rows per currency, so only the latest,
positive-price entry per currency is kept. A bundled snapshot
(`src/prices.json`) is used as a fallback if the live fetch fails.
- Token icons come from [Switcheo/token-icons](https://github.com/Switcheo/token-icons),
copied into `public/tokens/`. Any currency without a matching icon falls
back to a circular badge with its initials (see `TokenIcon.tsx`).
- Amount to receive is derived automatically from the live exchange rate —
no separate "calculate" step.
- Input validation: the send amount must be a positive number, and the two
sides can't both be the same token (enforced by disabling that option in
the token picker).
- A ⇅ button reverses the swap direction, carrying over the current output
amount as the new input.
- Submitting simulates a backend call (`setTimeout`) so the loading spinner
on the submit button is visible, then shows a success message — there's no
real backend for this challenge.
- Token pickers are searchable dropdowns (type to filter by symbol).

## Structure

```
src/
components/
SwapForm.tsx - presentational: renders the hook's state
TokenSelect.tsx - searchable token dropdown
TokenIcon.tsx - token icon with initials fallback
hooks/useSwapForm.ts - all form state, validation, and submit logic
utils/
loadPrices.ts - fetch + dedupe the price feed
format.ts - number formatting
data/fallbackPrices.ts
prices.json - bundled fallback snapshot
```
38 changes: 12 additions & 26 deletions src/problem2/index.html
Original file line number Diff line number Diff line change
@@ -1,27 +1,13 @@
<html>

<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Fancy Form</title>

<!-- You may add more stuff here -->
<link href="style.css" rel="stylesheet" />
</head>

<body>

<!-- You may reorganise the whole HTML, as long as your form achieves the same effect. -->
<form onsubmit="return !1">
<h5>Swap</h5>
<label for="input-amount">Amount to send</label>
<input id="input-amount" />

<label for="output-amount">Amount to receive</label>
<input id="output-amount" />

<button>CONFIRM SWAP</button>
</form>
<script src="script.js"></script>
</body>

<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Fancy Swap</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
Loading