{"id":47232094,"url":"https://github.com/jonlaing/fizzbuzzjs","last_synced_at":"2026-03-13T20:37:28.059Z","repository":{"id":47606607,"uuid":"99766843","full_name":"jonlaing/fizzbuzzjs","owner":"jonlaing","description":"A completely over-engineered FizzBuzz in Javascript","archived":false,"fork":false,"pushed_at":"2017-11-08T08:48:48.000Z","size":6,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2023-03-14T04:25:20.707Z","etag":null,"topics":["fizz-buzz","fizzbuzz","functor","javascript"],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jonlaing.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-08-09T05:04:34.000Z","updated_at":"2022-07-21T03:37:04.000Z","dependencies_parsed_at":"2022-09-20T05:04:19.878Z","dependency_job_id":null,"html_url":"https://github.com/jonlaing/fizzbuzzjs","commit_stats":null,"previous_names":[],"tags_count":null,"template":null,"template_full_name":null,"purl":"pkg:github/jonlaing/fizzbuzzjs","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jonlaing%2Ffizzbuzzjs","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jonlaing%2Ffizzbuzzjs/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jonlaing%2Ffizzbuzzjs/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jonlaing%2Ffizzbuzzjs/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jonlaing","download_url":"https://codeload.github.com/jonlaing/fizzbuzzjs/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jonlaing%2Ffizzbuzzjs/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30474944,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-13T17:15:31.527Z","status":"ssl_error","status_checked_at":"2026-03-13T17:15:22.394Z","response_time":60,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["fizz-buzz","fizzbuzz","functor","javascript"],"created_at":"2026-03-13T20:37:27.954Z","updated_at":"2026-03-13T20:37:28.045Z","avatar_url":"https://github.com/jonlaing.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"An Over-Engineered Fizz Buzz\n--------------------------------------------------------------------------------\nEver wanted to take a simple problem and completely over think it in a futile attempt to learn something about the world in your place in it? Well, anyway, this is about \"Fizz Buzz\" and how to completely over engineer it, and also some ramblings about abstraction.\n\nAnyone who has spent any time around tech knows what Fizz Buzz is. It's basically a bare minimum indication that someone can at least use `if` statements.\n\nIn case your your memory has escaped you, this is a more-or-less common implementation (in Javascript):\n\n```javascript\n// fizzbuzz.js\n\nconst fizzbuzz = (n) =\u003e {\n  let out = \"\";\n\n  if(n % 3 === 0) out += \"Fizz\";\n  if(n % 5 === 0) out += \"Buzz\";\n\n  return out || n;\n};\n\n// make an array from 1 - 100\nArray.from(Array(100).keys(), x =\u003e x + 1)\n  .map(fizzbuzz)\n  .map(console.log);\n```\n\nNow, that's about as basic as it gets. It's essentially one stop up from *Hello, World*. It may seem absurd, but this implementation always bothered me. For such a simple task, this implentation lacks a sort of elegance. It's also not generalized at all. Of course in the case of fizzbuzz, generalization isn't really the point. I mean, afterall this really just a test to make sure the programmer in question at least made it to page 2 in their copy of *Programming For Dummies*. However, in the professional world, we rarely have problems that have such specific and limited specs. In my experience, the brute force option will get you done faster, but will ensure that your solution doesn't scale as soon as your client/management changes their minds.\n\n## Robustness though Abstraction\n\nWhenever I'm given specifications, I'm immediately skeptical. The specs we're given are rarely the final product. Software specifications can be capricious, so the best option is to try and suss out the essence of the problem, which is the part that's least subject to change.\n\nIn the case of fizzbuzz, the essense of the problem is:\n\n\u003e For some number **N**, return **S\u003csub\u003e0\u003c/sub\u003e** if it is a multiple of **X**, otherwise return **N**\n\n\u003e For some number **N**, return **S\u003csub\u003e0\u003c/sub\u003e** \u0026oplus; **S\u003csub\u003e1\u003c/sub\u003e** if **N** is a multiple of **X** and **Y**.\n\nOkay, so the most generalized problem is that we need to make some function that will turn a number into a string if it is a multiple of a different number, and if we do it twice, and it's a multiple of both numbers, then we need to concatenate the strings. The fact that we're asked to do it from 1\u0026ndash;100 is a feature, not the core problem.\n\n## A First Draft\n\nThe optimal way to write this would be something like:\n\n```javascript\n// Looks nice, but won't work\nbuzz(fizz(2)); // 2\nbuzz(fizz(3)); // \"Fizz\"\nbuzz(fizz(5)); // \"Buzz\"\nbuzz(fizz(15)); // \"FizzBuzz\"\n```\n\nBut how would one even implement that? Based on this draft it looks to me like both `fizz` and `buzz` take numbers and return either numbers or strings. `buzz(fizz(2))` and `buzz(fizz(5))` are both fine because `fizz` returns a number in both cases. `buzz(fizz(3))` would be \"fine\" if we had a special case for `buzz` being passed a string. But `buzz(fizz(15))` is a problem, because `fizz` destroyed the number it was given, so `buzz` has no way of knowing if it was a multiple of 5.\n\n## Special Types\n\nIt looks like just numbers and strings aren't gunna cut it. So, lets make a new type:\n\n```javascript\nconst Fizzer = (n) =\u003e ({\n  value: n,\n  text: ''\n});\n```\n\nOkay, so now  we can keep track of the state of the numbers:\n\n```javascript\nconst fizz = (f) =\u003e (\n  f.value % 3 === 0\n    ? Object.assign({}, f, { text: f.text + \"Fizz\" })\n    : f\n);\n\nconst buzz = (f) =\u003e (\n  f.value % 5 === 0\n    ? Object.assign({}, f, { text: f.text + \"Buzz\" })\n    : f\n);\n\nbuzz(fizz(Fizzer(2))); // { value: 2, text: '' }\nbuzz(fizz(Fizzer(3))); // { value: 3, text: 'Fizz' }\nbuzz(fizz(Fizzer(5))); // { value: 5, text: 'Buzz' }\nbuzz(fizz(Fizzer(15))); // { value: 15, text: 'FizzBuzz' }\n```\n\nBut now, of course, there's a pattern here, so we can refactor:\n\n```javascript\n// a little bit of currying, because why not?\nconst fizzbuzzer = (div, text) =\u003e (fizzer) =\u003e {\n  // It's getting annoying having to always type `Fizzer(x)`\n  const f = fizzer.hasOwnProperty(value) ? fizzer : Fizzer(fizzer);\n  return (\n    f.value % div === 0\n      ? Object.assign({}, f, { text: f.text + text })\n      : f\n  );\n};\n\nconst fizz = fizzbuzzer(3, \"Fizz\");\nconst buzz = fizzbuzzer(5, \"Buzz\");\n```\n\nThe beauty of this is that now the `fizzbuzzer` implementation is completely divisor and text agnostic. We could just keep adding:\n\n```javascript\nconst baz = fizzbuzzer(7, \"Baz\");\nconst borf = fizzbuzzer(13, \"Borf\");\n\nborf(baz(buzz(fizz(7)))) // { value: 7: text: 'Baz' }\nborf(baz(buzz(fizz(21)))) // { value: 21, text: 'FizzBaz' }\n```\n\n## Scalability\n\nOkay, so this particular implementation is pretty good so far. It's generalized, agnostic, and composable. However, as more cases get added, it's going to become pretty unweildy. What if there were 10 cases? 20? That function call would get a little out of hand. Of course there's libraries like [Ramda](http://ramdajs.com/) that make function composition a breeze:\n\n```javascript\nimport R from 'ramda';\n\nconst fizzbuzz = R.pipe(fizz, buzz, baz, borf);\nfizzbuzz(65); // { value: 65, text: 'BuzzBorf' }\n```\n\nWell that's certainly much better. However, I feel like we've lost something in the readability here. It's not as clear from the definition of `fizzbuzz` what's happening. Even though it's terse, it's not *clear*.\n\n## Copy the Nerd's Homework\n\nWhen devising a suitable abstraction, it's sometimes a good idea to take some cues from the world of math. The world of mathematics is full of people solving the most abstract of problems, often just for the sake of their own curiosity (y'know, nerds). It's often a good place to look for abstractions. *Functors* are one such abstraction that seems to be infinitely useful, and I posit they'll be useful here. (Technically, I'm stealing this from Haskell, which I hear stole the idea from math nerds, but I can't confirm.)\n\nThe nice thing about Functors is that they give you a way to wrap up your values, and provide you an nice compoosable interface to mutate them. Since all Functors implement `map` we can just keep chaining along without fear of a runtime exception.\n\nThis works well for our scenario, because we need a way to mutate the values inside `Fizzer` without exposing too much, or destroying our nice composable interface.\n\nLet's see how we could implement fizz buzz with Functors:\n\n```javascript\n// some convenience functions for readability\nconst when = (pred, f) =\u003e (n, text) =\u003e pred(n) ? f(text) : text;\nconst isMultiple = x =\u003e y =\u003e y % x === 0;\nconst concatString = s1 =\u003e s0 =\u003e s0 + s1;\n\n// implementing the Functor\nconst Fizzer = (n, text) =\u003e ({\n  map: f =\u003e Fizzer(n, f(n, text || '')),\n  done: () =\u003e text \u0026\u0026 text.length \u003e 0 ? text : n // get out of the functor\n});\n\nFizzer(35)\n  .map(when(isMultiple(3), concatString(\"Fizz\")))\n  .map(when(isMultiple(5), concatString(\"Buzz\")))\n  .map(when(isMultiple(7), concatString(\"Baz\")))\n  .map(when(isMultiple(13), concatString(\"Borf\")))\n  .done(); // \"BuzzBazz\"\n```\n\nPersonally, I like this. It's really straight forward, and actually increases the flexibility of the implementation (which could come in handy when your boss/client has a last minute feature they want to add to the spec).\n\nFor instance, now the spec has changed, and you need to internationalize fizzbuzz. No problem!\n\n```javascript\nFizzer(13)\n  .map(fizzbuzz(3, \"Fizz\"))\n  .map(fizzbuzz(5, \"Buzz\"))\n  .map(fizzbuzz(7, \"Baz\"))\n  .map(fizzbuzz(13, \"Borf\"))\n  .map((n, text) =\u003e i18n(text))\n  .done(); // \"Το σκυλο μιλει, \\\"Μπορφ!\\\"\"\n```\n\n## Array of Sunshine\n\n The last part of the spec, was that it was supposed to print fizz buzz from 1-100, so let's do that. However, we know better than to just implement code exactly to the specifications. You're gunna finish all that work, and then your boss/client is going to say, \"Oh, wait, actually, we want fizz buzz from 3 to 491\". And what are you gunna say? \"No problem! (if you pay me)\"\n\n```javascript\nconst range = (min, max) =\u003e\n  Array.from(Array(max - min + 1).keys(), x =\u003e x + min);\n\nconst fizzIt = n =\u003e\n  Fizzer(n)\n    .map(fizzbuzz(3, \"Fizz\"))\n    .map(fizzbuzz(5, \"Buzz\"))\n    .map(fizzbuzz(7, \"Baz\"))\n    .map(fizzbuzz(13, \"Borf\"));\n    .done();\n\nconst fizzBuzz = (min, max) =\u003e\n  range(min, max)\n    .map(fizzIt)\n    .map(x =\u003e console.log(x));\n\nfizzBuzz(1, 100);\n```\n\n## All done\n\nOkay, so we've had some fun, some laughs and some tears, but in the end we wasted quite a bit of time over a trivial problem, and we need to wrap this up. Since the spec only said that we needed \"Fizz\" and \"Buzz\", that's all I have here. However, because I took the time to think about the most general problem, I know have a flexible api that can be extended pretty far without having to refactor. \n\n```javascript\n// fizzbuzz.js\n\n// some convenience functions for readability\nconst when = (pred, f) =\u003e (n, text) =\u003e pred(n) ? f(text) : text;\n\nconst isMultiple = x =\u003e y =\u003e y % x === 0;\n\nconst concatString = s1 =\u003e s0 =\u003e s0 + s1;\n\nconst range = (min, max) =\u003e\n  Array.from(Array(max - min + 1).keys(), x =\u003e x + min);\n\n// implementing the Functor\nconst Fizzer = (n, text) =\u003e ({\n  map: f =\u003e Fizzer(n, f(n, text || '')),\n  done: () =\u003e text \u0026\u0026 text.length \u003e 0 ? text : n // get out of the functor\n});\n\nconst fizzIt = n =\u003e\n  Fizzer(n)\n    .map(when(isMultiple(3), concatString(\"Fizz\")))\n    .map(when(isMultiple(5), concatString(\"Buzz\")))\n    .done();\n\nconst fizzBuzz = (min, max) =\u003e\n  range(min, max)\n    .map(fizzIt)\n    .map(x =\u003e console.log(x));\n\nfizzBuzz(1, 100);\n```\n\n## A Word About Abstraction\n\nOkay, so this was obviously overkill for fizzbuzz, but I hope you got something out of the excercise. Abstracting to the most general form of the problem can help us construct future-proof(ish) implementations. It's upfront effort that will allow you to be lazy later.\n\n**However**, there is definitely a dark side to this. I've inhereted enough convoluted codebases in my time to know that not all abstraction is created equally. Some people will abstract a concept to the point of absurdity, and that doesn't help anyone. Abstraction should be *illuminating* not obscuring. Abstraction should allow *more* flexibility and composability, not less.\n\nSometimes, a less general implementation is better, because it illuminates the problem and solution better.\n\nSo, if your abstraction is hard to read and extend, **it's a shit abstraction** and you should go back to the drawing board before you get locked into it.\n\nBut, if you can strike that balance, then it will benefit you and your team in the long run.\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjonlaing%2Ffizzbuzzjs","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjonlaing%2Ffizzbuzzjs","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjonlaing%2Ffizzbuzzjs/lists"}