Category Archives: Blog

Chaos Theory in a Nutshell

In this In A Nutshell series, I briefly summarize and break down the main ideas in Chaos Theory, which is the study of how deterministic systems can produce unpredictable results and is also considered one of the “complex sciences”.  It’s a subject that I’ve become fascinated with in addition to fractal geometry, and thought it would be interesting to see how these ideas can be related to software engineering.

What is a Chaotic System?

Chaotic systems in a nutshell are deterministic systems that you would think are predictable, but wind up producing wildly unexpected output. The weather is an example of a chaotic system and the famous colloquial example of chaos is The Butterfly Effect, for example: a butterfly in China can flap its wings which eventually causes a hurricane in New York.

Key Terms:

  • Iteration: Doing the same thing again and again using the previous step’s output as the next step’s input. Note: Iteration is not repetition. In iteration, the output is used as the next input whereas in repetition, this does not occur.
  • Seed: the initial condition or input to a dynamical system at the start of an iterative process.
  • Orbit: Also known as an Itinerary, it is the various outputs in sequence starting from iterating from a seed.  You can plot and graph these outputs as they change over time and get insight into the behavior of various systems. Chaotic orbits become unpredictable and erratic on the plot.
  • Period: A repeated cycle or pattern in an orbit. Periods can have a length or duration, for example,  an iterated function that produces 2 values that oscillate back and forth as iteration continues has a period of 2.
  • Dynamical System: A system where some variable or set of variables changes over time. Iterated functions whose outputs change over time are one type of a dynamical system.

Chaotic systems have four main properties:

  • Sensitive Dependence on Initial Conditions: This means that if you change the starting condition of a system by a little bit, it will produce a much different outcome.
    • Think of a product line conveyor belt, for example, where you set the speed to 2.1 feet per second vs. 2 feet per second. You would think increasing the speed by 0.1 ft./s would produce a proportional increase (i.e. 5%) in output from the line.  But, imagine that after doing this you get a surprising 37% increase in output.
    • So a small change in the starting condition produced an unpredictable result compared with that of another miniscule different starting condition.
    • To further illustrate the importance that iteration plays in these dynamical systems, imagine that as each product gets to the end of the line, it is fed back into the start of the same conveyor belt system, and at the end, that resulting product fed back in at the beginning again, and this cycle is repeated (iterated).
  • Bounded: There are boundaries in the system output. The conveyor belt speed cannot be increased to infinity, for example.
  • A-periodic: There is not a constant repeated pattern so that you can predict how the system behaves long into the future.  The orbit or itinerary of the system does not contain any periods or repeated patterns going forward in time.
  • Deterministic: This means that if you plug in the same inputs to a system, you get the same outputs. So imagine that if we placed an unrefined product on the conveyor belt, we would always get the same refined product at the end (given the same unrefined product).

Behavior of Chaotic Dynamical Systems and Universality:

One of the most interesting things about Chaos Theory is that amidst the chaos in these systems there is also a strange order and universal behavior between systems. For example, something known as a bifurcation occurs as systems transition from ordered to chaotic states.

An example of this is a dripping faucet. As you slowly increase the flow speed of the stream, the drips will predictably start falling twice as fast, and then as you increase the flow more, twice as fast as that, and the rate continues to double until the rate of the drips are unpredictable and go into a chaotic state. This is measurable and found in many chaotic systems from unrelated domains as a consistent observed behavior.

Feigenbaum’s Constant:

What’s even more interesting and strange is that as these doubling periods increase, their proportional duration to each other will start to remain constant (a ratio known as Feigenbaum’s constant which is about 4.669). So the length of a doubling period will constantly wind up being 4.669 times the length of the following doubling period and this continues until the chaos state comes about.

Implications of Chaos Theory applied to Software Engineering:

Planning and Estimating, i.e. Waterfall vs. Agile:

One recurring property that exists in a lot of chaotic systems is that their trajectories (or orbits) can be predictable in the short term if compared with the trajectories of the same system with slightly different starting conditions.  But, these trajectories will diverge wildly as time progresses.  So, given this observation, the key in reliably predicting the behavior of these systems is to focus on short term timelines (consider that the weather, a chaotic system, can only be predicted reliably a relatively short time into the future).

Applying this idea to software development, this warrants caution against predicting or estimating outcomes in the distant future, considering the chaos and unpredictability that can unfold as time and development progresses.   Waterfall planning, for example, should be avoided and the pitfalls of putting ill-invested faith in these long-term estimations are well known.

Likewise, putting too much faith into estimations using a previous project’s trajectory to predict an upcoming similar project’s trajectory is also not advisable.  If the development of a software system is chaotic, as I suspect it is, then a small difference in the upcoming project compared to the previous similar project could result in wildly different trajectories.  It reinforces the importance of short iterations and frequent feedback loops to reset our trajectory constantly to look at a short time interval into the future, so that the results of our efforts are somewhat predictable.


As Tom DeMarco puts it in his book, Structured Analysis and System Specification (an oldie, but a goodie!), the brain operates naturally and most effectively as an iterative processor.  The fact that complex dynamical systems found all around us and in nature can be built from simple iteration is further proof its effectiveness.  One of the pieces of advice about how to build and design complex systems in the book is to simply start.  Put something down on paper, even if it’s bad, and then iterate on it over and over again to continuously improve it little by little until it takes shape into a well formed and robust design.

We shouldn’t underestimate the power of iteration.  The results of iterated chaos games and the resulting fractals from iteration like the Barnsley Fern, for example, show us the elegant and complex output that can be produced from simple iteration.

I can also see a parallel with Test Driven Development (TDD), in that a simple iterative feedback loop (Red, Green, Refactor) can produce an emergent design as the process continues.  This also mirrors at least in part what happens in chaos games, like the famous one that produces a Sierpinski Triangle; An elegant fractal emerges by iterating over simple rules.

Building Complexity from Simple Components:

More technically speaking, the idea of the recursion and iteration of deterministic functions in software and their similar counterparts and use in analyzing dynamical chaotic systems is noteworthy.  The idea that the iteration of simple functions can produce infinite complexity (see the Mandelbrot set) is food for thought and is a reminder that the best way to build complex systems elegantly should be to try to use simple building blocks and compose them.

Deterministic functions are also important in the functional programming paradigm.  Even though there are some random and stochastic elements found in the rules for Chaos games, you could still think of them as being input to a deterministic function.

An interesting thought experiment might be to ask how one might build complex programs or systems in a fractal or recursive manner.

Final Thoughts:

One of the things I find profound about the study of chaos is that these systems are observed occurring in nature and in mathematics and suggest that outcomes are not necessarily pre-determined, or at least you can’t assume outcomes or predict them in a lot of cases.  Like Keynes’ observation that true probabilities can only be calculated in theory due to inherent uncertainty, Chaos Theory gives me a scientific excuse to be pleasantly surprised and optimistic in life.


  • Chaos and Fractals: An Elementary IntroductionThis blog post was based on concepts introduced to me in this great book on Chaos Theory and Fractals by David Feldman. Highly recommended as a good introduction to the domain of Chaos Theory and in addition some fascinating information on fractal geometry is provided as well.

  • Check out my GitHub repo with demonstrations of things like the Chaos Game implemented in Python.

Unit Testing In a Nutshell

Over the last several months I have been taking a deeper dive into software testing best practices.  Testing is a common “pain point” for a lot of developers and I’ve seen multiple projects that even have no tests at all because “it slows us down”. I’m a big believer in testing (and test driving) software to reduce bugs, enable confident refactoring and improve the design of the system.  Having no tests or poor coverage will really slow projects down in the long run. This post distills some best practices for Unit Testing and my hope is to make a case for embracing testing as a tool to help us build better software and make our lives easier, not more difficult.

Unit Testing in a Nutshell:

  • A unit test verifies a small piece of the system, runs fast and is isolated from other tests.
  • Unit tests should verify units of behavior and not just lines of code or implementation details. This makes the tests resistant to refactoring so they don’t break when implementation, but not functionality, changes in the system.
  • Unit tests can serve as documentation or a kind of Spec for the system and ideally should be written in a way that even a non-developer or business person can understand (i.e. the description of the test should describe a business scenario in plain English)
  • The purpose of unit tests are to enable sustainability of the system (to keep it from grinding to a halt due to bugs or breaking changes in the long run and to allow for refactoring and making changes with confidence)


  • Spec: Short for Specification. A description of what the software or system should do to satisfy stakeholders goals.
  • Unit of Behavior: A small grouping of code representing some behavior of the system. Unlike a unit of code (i.e. a single function or class), a unit of behavior can span multiple classes or objects.
  • Regression: A bug. When a feature stops working after a certain event (i.e. a code change etc.)
  • System Under Test (SUT): The part of the code in the system that is being tested. i.e. the classes, objects or functions.
  • Test Isolation: The ability for tests to run in parallel, sequentially, and in any order is called test isolation.
  • False Positive: A test that fails, but for the wrong reasons. This term can be confusing, so think of a test for the flu – if you test positive, that’s bad and means you have an illness, so here “positive” counter intuitively translates to a negative (i.e. a test failure) result.
  • False Negative: A test that passes, but for the wrong reasons. 
  • Test Double: A Mock, Stub, Spy, Dummy or Fake to mimic a dependency instead of using it directly in a test. The term comes from “Stunt Double” used in film making.  A future blog post will explore these in more detail.

What makes a good test? The properties of a good unit test:

  1. Resistant to regressions (Catches bugs.)
  2. Resistant to refactoring (The test is not brittle and will not break when code implementation, not functionality/behavior, changes.)
  3. Runs fast for rapid feedback (You should be able to keep unit tests running during development to verify changes do not break something as you’re coding.)
  4. Easy to maintain
  5. Isolated (The test can be run independently of other tests and in any sequence of order. This allows for parallel execution for faster test runs and eliminates unexpected bugs caused from shared states.)

Do not focus on achieving 100% Line Coverage, focus on coverage of Behavior and Use Cases

One of the benefits of good test coverage is to give you confidence that changes to the system (i.e. refactoring or adding features) does not break it’s desired behavior.

Focusing on achieving 100% line coverage can, counter-intuitively, actually be problematic because it forces you or your team to write tests just to achieve that benchmark, whether the tests are valuable or not. This also has the side effect of incentivizing testing implementation details making for brittle tests that will break when refactoring occurs. You will start getting more and more false positives from your tests which will reduce confidence in them.

Not only does this create extra work unnecessarily, it also makes developers hesitant to make changes and refactor because they will then need to fix a bunch of brittle tests. The code stagnates and rots over time.  Line coverage requirements are not necessarily a good idea and focusing on testing the observable behavior of the system will make for more valuable tests to cover what really matters.

Unit Tests need to run fast

You should have a fast running suite of tests that you can leave on watch mode while developing. The purpose of this is to enable you to catch bugs and breaking changes as soon as they happen. This greatly decreases debugging time because it saves you the trouble of hunting down exactly which part of the code broke something. If you have the tests running and they start failing, the part of the code that broke things was….the last change you made. This quick feedback loop will save lots of time that would otherwise be dedicated to debugging.

A Note on Naming Tests:

You should try to name your tests in such a way that a business person or domain expert would understand what the test is covering. This helps to make your test suite a documentation or spec for the system and what it’s expected functionality is. This in turn also makes the system as a whole and what it’s goals are easier to reason about and understand.

Suggestion:  Tests should verify facts about the system. You may want to consider removing words like “should” and replacing with “is”, and when naming tests in general consider making a statement as if it were a fact about the behavior of the system.  The C# testing tool XUnit, for example, actually uses [Fact] to declare a test definition.

Another suggestion is to just use plain English when naming tests instead of following a convention like: [MethodUnderTest]_[Scenario]_[ExpectedResult].
You shouldn’t use the name of the method or class in the test description because that is an implementation detail. If the function name changes, then you have to update all of the test names referencing it.

The Classical School vs. London School of Unit Testing:

There are two schools of testing: The Classical School and The London School.  There are pros and cons to each, but I prefer the Classical School style. It is advocated by industry stalwarts such as Martin Fowler and the arguments for it, in my opinion, are compelling.

  • The London School prefers using Test Doubles for all dependencies except immutable objects vs. the Classical School which only mocks shared dependencies
  • The London School considers a unit a class or function, while The Classical School considers a unit a unit of behavior which could span multiple classes or functions.
  • The London School considers a test isolated if the class or unit is isolated completely from other classes through mocking all outside dependencies, while The Classical School considers isolation of an entire test (the unit of behavior is separate from another unit, dependencies for classes may not be mocked).

One of the main advantages of The London School of testing is that if a test fails, you know exactly where and what part of the code failed since it considers a unit to be very granular (a specific isolated class or function). The disadvantage is that more mocking makes for more fragile and brittle tests.  Since mocking everything except immutable objects, including intra-system communication, will require tests knowing more about implementation details instead of only verifying observable outcomes, they will be less resistant to refactoring.

Even though The Classical School of testing has some disadvantages like the lack of easily finding the specific failing function or class, these can be overcome by running the tests constantly to give you instant feedback when a breaking change is introduced – the source of the bug is easily found, it’s the last thing you wrote. The main advantage is that by testing observable behavior and outcomes, your tests become more resistant to refactoring, valuable and test the meaningful parts of the system.  This also provides important feedback about pieces of code that cause cascading failures across the system. The London School approach would only indicate the specific class that failed since the classes under test are completely isolated.


  • Unit Testing: Principles, Practices and Patterns by Vladimir Khorikov. An excellent book on best practices and approaches to Unit Testing including when and what to mock, what to test and more. This blog post was inspired by and based on ideas in this book.


BEM is a naming convention for CSS classes that facilitates writing clean and organized CSS.  This blog post will boil down what BEM is and how to use it in this “In a Nutshell” series entry.

BEM In a Nutshell:

  • BEM stands for Block, Element, Modifier, each of which represent the 3 parts of a class name using this convention.
  • The format for the BEM naming convention is <Block>__<Element>--<Modifier> where Block, Element and Modifier represent a part of the class name (see Definitions below).

The reason BEM is a useful naming method is because it explicitly describes which element(s) your CSS rules apply to and encourages incorporating Separation of Concerns in your CSS. This makes maintaining and changing your CSS much easier to do, since making a change for one class named using BEM will not inadvertently and unexpectedly break or change the presentation of other elements.


  • Block: A name that describes an HTML element that can be independently moved around in the HTML document and not lose it’s meaning.
  • Element: A name that describes an HTML element that is a child or descendant of a Block element and depends on that Block element to retain it’s meaning.
  • Modifier:  A name that describes an alternate state that an element can get into. For ex., a disabled state.


Let’s say you have a form that is part of a login page on your website.  We want to use BEM to construct class names for various elements to apply CSS.
The following is an example using the BEM naming convention to apply class names to the various elements:

<form class="loginForm">
     <input type="text" name="username" class="loginForm__username-input" />
     <input type="password" name="password" class="loginForm__password-input" />
     <button type="submit" class="loginForm__submit-button--disabled">Log in</button>

loginForm is the Block element class name because the form that it labels can be moved anywhere in the document and still retain it’s meaning – it is the form specifically for logging in.

The username and password inputs are not independent of the login form, so they are labeled using the Element name part in the class name.  This part is preceded by the Block they depend on (loginForm) followed by a double underscore, so they are labelled with descriptive class names such as: loginForm__username-input.

The submit button of the form starts out in a disabled state, so we add a Modifier name to the class name we will use to set the CSS for it.  The Modifier name describes the state of the element preceded by two dashes, the Element it belongs to and the Block followed by two underscores: loginForm__submit-button--disabled.


  • – The “Clean Code in the Browser” Series is an excellent video course which demonstrates not only how to use BEM for clean CSS, but how to apply Clean Code and SOLID principles to your HTML/CSS and JavaScript code.  This blog post was based in part on the video discussing BEM.

Data Directed Programming – A Useful Pattern

A common problem in software development is how to deal with multiple but generally similar objects that differ in some way requiring handling, processing and application behavior specific to their subtype.  After going through a free MIT lecture series taught by  Hal Abelson and  Gerald Jay Sussman on the Structure and Interpretation of Computer Programs, I came away learning a very useful software design pattern that I have used in multiple projects now to deal with this situation.

In the lectures, it is sometimes called “Data Directed Programming” or “Dispatch on Type”, which is a way of handling multiple objects that may be related and the same in some general sense, but differ slightly requiring targeted processing and handling for them.  Think of cars like Hondas, Toyotas and Fords;  They are all cars and similar in a general sense – they all have brakes, transmissions and tires etc., but you might need to take a specific make to a specialized mechanic to perform certain repairs, because the specifics (i.e. the implementation) of those components can differ requiring special knowledge in order to work on them.

In software development you will at some point inevitably run into a situation where you are dealing with objects of a general type that require different processing based on their subtype.  This usually leads to all sorts of conditional gymnastics and if/else branches that can become complicated and difficult to understand and maintain.  That’s where this pattern can come in to save the day, keep your code clean and get you out of that mess.

Data Directed Programming – The Basic Pattern:

  • Keep a table or dictionary of types that map to procedures.
  • When you call a general procedure that would apply to a series of related objects with different subtypes, use the type as a lookup to find the corresponding behavior.

Let’s try to make the pattern concrete with an example (forgive me if it’s a little contrived, but it will get the point across and you will recognize when to use this pattern in the real world).  Imagine we have a chat app and we want to process messages that have the same overall shape, but can fall into subtypes such as Private, Broadcast or Group typed messages which differ in a relatively small way and require different handling.

We want to have a single re-usable procedure (or function) that handles message processing – one that we can call when a message comes in.  Let’s see how this looks without using Data Directed Programming (we’ll use vanilla JavaScript for the examples):

// General reusable message processing procedure
const onMessageSent = (message) => {
  // check if message subtype is private and handle that specific processing:
  if (message.type === 'private') {
    ...private message specific processing
  } else if (message.type === 'broadcast') {
    ...broadcast message specific processing
  } else if (message.type === 'group') { message specific processing
  } else { 
    throw Error('Message type not recognized.');

Note the multiple conditional branches we need to handle the different subtypes of messages appropriately.  It’s not horrible in this contrived example, but it can quickly get out of hand in real world situations and is generally just a bad idea to have complex if/else chains if they can be avoided.  This is where our pattern helps out to eliminate that.

If we make a table and use the message subtype as a primary ID key, we can just pass in the subtype and use the procedure that maps to it directly without having to make conditional branches in our code.

// Make a table, i.e. a dictionary, with keys of message subtypes that map to specialized procedures for that type:
const processMessage = {
  private: (msg) => ...private msg processing,
  broadcast: (msg) => ...broadcast msg processing,
  group: (msg) => msg processing

// Our general message processing handler uses the dictionary and looks up the processing we want by subtype, then calling the processing procedure:
const onMessageSent = (message) => {  

As you can see, this is quite a bit cleaner and we’ve gotten rid of all those conditional branches that can be difficult to read and understand.  In my opinion,  good software design and clean code can be based on this one question: Will someone who has never seen this code before be able to understand it quickly and easily?

I can’t stress enough how useful this pattern has been in many of the applications and products I’ve worked on.  This is a common scenario that too often ends up with complicated conditional branching. Obviously you don’t want to overuse any one pattern, but I have found it works well when this common situation arises.

Further Resources:

React-Native – Fixing missing GoogleMaps API Key error

The Problem:

After pushing my Android app built with React-Native and Expo, HereHere, to the Google Play Store recently, the app froze when accessing a page that uses the Google Maps and Google Places API.  After looking at the logs, I noticed an error message indicating a missing API Key in the Android Manifest.  The error stated that android.config.googleMaps.apiKey is missing.

The Solution:

It turns out that after upgrading my app to use the latest version of the Expo SDK, there is a required config variable for the Google Maps API that needs to be set in your app.json file.

*NOTE: My project was built using Expo’s managed workflow.  If you built your project without Expo, you may need to look at updating your AndroidManifest.xml file instead and making sure the API key is set there.

  "expo": {
    // other props...,
    "android": {
      "config": {
        "googleMaps": {
           "apiKey": "[your api key here]"
      "package": "...",
      "permissions": [...]
    "sdkVersion": "38.0.0"

The tricky thing about this was that the app worked fine in development and the problem only arose after I pushed to the Google Play Store and downloaded the app to my phone.  The reason was that my app was using a local config file to get the API key, and the production build looks for the value in app.json to inject into the Android Manifest file.

To try to prevent these kinds of problems in the future, you should publish your application to Expo first using the expo-cli tools ($ expo publish).  You can then access the app in a more production like environment and see if any issues arise before pushing to the Google Play Store to find out about it only when it goes live.

Further Resources:

STOCK GLASSES – Focused and Simplified Stock Analysis

“Everything should be made as simple as possible, but no simpler.”

-Albert Einstein

I’ve been working on a project to play around with Python and Flask and it snowballed into a full blown web app which I’ve deployed.

The app is called Stock Glasses, and it’s purpose is to provide a tool for focused and simplified stock analysis based on concepts in respected financial investing books such as “The Intelligent Investor” by Benjamin Graham and “The Intelligent Asset Allocator” by William Bernstein.

Use the working app here –

Follow on Twitter: @GlassesStock

Also see the Stock Glasses YouTube Channel for demonstrations of using the app to analyze stocks.

Inspired by First Principles thinking and the quote by Einstein above, the goal is to eliminate noise and focus on the essentials to evaluate a company.  Basically the app focuses on analysis of these fundamentals:

  1. Top Line – Revenue
  2. Bottom Line – Profit/Net Income
  3. Financial Health – Current Assets vs. Total Debt

For a full explanation of how to use the app, visit the How To Use Page of the app.

Future plans for this project include turning it into a progressive web app for offline use and playing around with comparison analysis further.

The app was built with React/Redux, Python Flask and utilizes Firestore for persisting data.

I welcome any feedback, particularly from finance people who might be able to  critique or offer suggestions/improvements on the metrics and analysis tools offered by the app.

Usual disclaimers apply: I’m not a financial advisor or pro investor, so use and invest at your own risk.  I do think it is a potentially useful tool for learning about basic stock analysis, understanding some common metrics and a good introduction to Value Investing.

Additional resources which inspired the app:



How I Find Clients as a Freelance Web Developer

I’m often asked, “How do you find your clients?”.  This blog post will attempt to answer that question.  The simple answer is, I look anywhere and everywhere all the time.  Freelancing is tough, make no bones about it.  I basically spend all of my time during the day working on client projects and after that, the work isn’t done.   I’d like to share what I’ve learned about how to approach and engage with potential clients, and what has proven to be the most effective strategies and methods for finding clients ‘in the field’.

I’ll first list and illustrate a few ways I try for approaching (or being approached) by potential clients.  The second part of this post will address breaking the ice with people and approaches to conversing with them since this is something that some people find difficult about being a freelancer.  In the final section I will list my conclusions on which methods seem to work best as I have been taking note of my interactions to try to determine what approaches work better than others.

Ways of Searching for and Finding Clients:

1. Bring a web development or programming book with you, wherever you go and put it on the table or counter in front of you so it is visible to others.

I’ve found this so far to be an extremely effective way to open up a dialogue with people.  It’s a great conversation starter.  My book of choice is Refactoring by Martin Fowler.  It has a nice big title (and is a cool word that intrigues people) and is large enough that the book can be seen from a distance.  I’ve taken this book into many different places and even carried it on the street so the title is visible and almost without fail will be approached by others to ask about what I’m reading.  Even bringing it into a cowboy bar downtown, I was approached by multiple people, one of which was a developer with their team and said how happy he was to see someone who knew who Martin Fowler was there.  I wound up handing out 3 cards that night.  Seriously, try this.  The key is that it attracts people who are either interested in technology or interested in people who are interested in technology because…maybe they need a website.

2. Find out if there are large conferences going on in your area, what hotel or area most of the attendees stay at, and make the rounds there.

This is a very effective way to look for clients because usually there will not only be attendees of the conference, but other business owners and representatives there that may need website work.  I’ve found you can strike up a conversation with many people involved in the event if you go to the hotel where most people stay and hang out there.  I recently attended the Stock Show in Denver and it proved to be a great place to meet business owners and people who might need website work done.

3. Go to venues that are related to an interest you have.

I love jazz and hearing live music, and it just so happens that other people do as well.  It may also be in the cards that one of these fellow music lovers may need a website or web development work.  Just by being in a place patronized by other people with a similar interest, I immediately have something in common with everyone there and an easy way to open up a dialogue and conversation which could potentially lead to meeting and working with a client.

4. Go to Meetups

Tech meetups are great for finding potential clients.  Particularly ones that focus on your areas of expertise or ones that are more social in nature than presentational.  I found one client going to a React meetup.  Usually at these meetups there will be a portion of time dedicated to letting people looking for developers make a quick announcement.  In this case, someone was looking for a React developer for their project and we exchanged information and have been collaborating ever since.  Another meetup in the Denver area, Denver Develop Happy Hour, is great for networking and meeting potential clients because it is entirely social in nature – there are no presentations or lectures, just a bunch of people in tech or interested in tech in one place hanging out and socializing.  I met another client looking for a developer for their project at this meetup.  Since I also play drums, I sometimes go to music jam meetups, so if you have another interest (i.e. hiking, painting, books etc.), then it may be worth it to go to those as well to not only meet people with similar interests and make new friends, but potentially meet new future clients.

5. Experimental: Find a venue hosting an open mic and put on a performance

If you can sing at all (or even if you can’t – it doesn’t matter since it’s an open mic and not a professional performance), or if you can do stand up maybe, find an open mic night and perform.  At the end of the  performance, if the owner or event host won’t get too mad, make a quick announcement that you are a web developer and invite anyone that needs help with their website to come talk with you.   This is a recent discovery and method I’m trying and going to keep experimenting with, but after putting on an A cappella performance at an open mic night, I realized that this provides you with the opportunity to essentially have a captive audience of everyone in the room.  Instead of having to make the rounds and find out who might be looking for your services, you can just ask literally everyone there in one fell swoop.

6. Look at Linkedin feeds and social media feeds

One day I just happened to see a post from someone looking for a developer for their project.  This was by accident initially, but now I actively look at social media feeds (especially Linkedin) in case someone posts a request for a developer they need for work.  Reddit also has a ForHire subReddit that is worth checking out.

How to Converse and Engage with Potential Clients:

Note: Some of these principles may be more effective outside of the context of a conference or networking event where the sole purpose is to network and the participants all possess or are looking for a particular skillset.   They might be more useful ‘in the field’ when you are looking for clients outside of the contexts mentioned above in more everyday situations (since as a freelancer, depending on your situation there may be times you have to look for clients more or less every day).  Also, it goes without saying that at some point you need to identify the problem a potential client has and offer ways you will solve it – that is a given and not the focus of this blog post.  The principles outlined below are more about how to start the dialogue with this person at all, which can be a pain-point for some people, and eventually get to that point in the conversation.

Key Principles:

  • Be Patient:  Often, just sitting and enjoying yourself without actively engaging others can work just fine and be a good baseline to operate on.  Good things come to those who wait –  I’ve found that if you are trying too hard to engage potential clients, it can look a little desperate or simply be inappropriate to bother people and so it can wind up turning people off.  Just being patient and observant (see below) and being relaxed makes you more approachable and usually this invites others to open a dialogue or at least doesn’t put them off.


  • Be Observant:  This is a sibling to the principle above (Be Patient).  You need to be able to sense when to engage with another person.  Sometimes, people may actually want others to engage with them and you’ll have to read body language and use your intuition to tell this – it can be fairly obvious (they constantly turn to look at you, or make comments out loud to nobody in particular about the venue or what’s playing on the TV).  This is usually an implicit invitation to engage in a dialogue with the person or a sign that they do not wish to be left alone, and you should seize this opportunity.  Make your responses to their comments or glances brief at first to test the waters, and then things should open up from there.  In addition, if you over hear someone nearby speaking about a common interest, then this is also an opportunity to engage in a dialogue about that interest – you’ll have to use your discretion on when to approach them about the subject so as not to interrupt.


  • Enjoy the Thrill of Taking a Risk and Exploring the Unknown: It goes without saying that not every conversation or engagement with a potential client will go well.  This should not be something to be feared, but embraced.  You need to enjoy the adventure and thrill of not knowing how things may turn out.  This is exciting – think of it as being like Indiana Jones going into the Temple of Doom (or pick your favorite hero) and daring to embark where others fear to tread dodging all sorts of booby traps and steamrolling boulders along the way.  You are an adventurer, like Lewis and Clark on an expedition to explore the unknown.  Even if there is danger of failure, you have to remember that there is the potential of wild success and the discovery of something great just as well.   I find it helpful to romanticize my excursions to find clients in this way.  For the most part, the worst that happens is an interesting conversation with someone you haven’t met.


  • Do Not Make a Pitch Initially or too Quickly: Making the pitch too quickly, or sometimes even at all turns people off and puts them on the defensive; Nobody likes pushy people and most are usually skeptical of salespeople.  I’ve found it much more effective to make having a genuinely enjoyable conversation and developing a genuine interest in the person you’re speaking with your goal, and then at some point along the way as the conversation progresses, usually you’ll wind up asking each other what you do for a living and that can open the door for mentioning your web development services.  They may need a website, or not, and respond accordingly.  If there is not explicit interest, then at least you’ve had an enjoyable conversation, maybe learned something new and you can tell them how much you enjoyed talking with them and casually hand them your card saying if they have any need in the future or know anyone who does, feel free to get in touch.  And they just might…


  • Find Topics of Common Interest to Talk About:This could be the kind of music you both enjoy, places you’ve been or traveled to, being curious about where they’ve lived, favorite foods or drinks, etc.  This typically should be the focus of the interaction.


  • Be Genuinely Interested in the Person You Are Speaking With:  This is probably the most important fundamental principle and idea to successfully engaging in conversation with others and potential clients.  At the end of the day, people actually are incredibly interesting, each like a complex Symphony with different perspectives and experiences from your own that you can learn from.  People are mind-bogglingly fascinating and realizing this is key to enjoying the process of taking part in and looking forward to opening up a dialogue.

Concrete Examples of Conversations I’ve had while looking for clients:

These are examples of actual conversations I’ve had while making the rounds looking for clients.  My hope is that they provide a real life example of how these dialogues can begin and give a feel for how one can start a connection with someone who may need your services. Some of the details of the conversation have been altered to make the parties involved anonymous.

  • At a venue watching a basketball game involving the Utah Jazz:
Potential Client: "So, who are you rooting for?"
Me who doesn't follow sports:  "The Utah Jazz, just because they have Jazz in their name."
Potential Client: "So you like Jazz?  Me, too.  Where can I go to hear some jazz around here?"
Me: "...[tells him where to go]"

And the conversation evolves from there during which a question of what each other does inevitably comes up.

  • This was during the Stock Show where I met a potential client in the Brown Palace needing work for his website.  He had sat down to order food and while he was waiting for the order to come out he seemed unoccupied and approachable, so I leaned over and asked:
Me:  "Are you involved with the stock show?"  
Potential Client: "Yeah, are you here for it? 
Me: "Yes..."
Potential Client:  "Did you go to the rodeo, you don't look like a rodeo rider... "
Me: "Definitely not.  I've just been checking things out since a couple I met recommended going to the Stock Show.  What are you involved with here?"
Potential Client: "I'm selling [supplies]..."

He then tells me about where he is from and being really curious about the place, I ask about what it’s like to live there and if there is a tech scene.  He tells me about it and then mentions that he has a website he is not quite happy with and might be able to use my help.

  • Again at the Stock Show speaking with an owner of a company that sells medical equipment.

I was sitting at the hotel bar waiting patiently and a gentleman comes up and orders a Scotch.  I thought it would be a good conversation starter if I ordered the same drink and then ask him how he liked that brand of Scotch to confirm that I made a good choice.   This starts a conversation about Scotch and I tell him about Bruichladdich which was recommended to me by a friend from Scotland years ago (just to be clear, I don’t make things up – I really had a Scottish friend recommend that Scotch to me).  He writes it down on a napkin which leads to a discussion about what we’re doing at the Stock Show.  I’ve found that telling people you are directly trying to find clients is not effective and has a similar effect to making a pitch too early.  Instead I tell him that a couple I met recommended I check the Stock Show out (which, again, is true).  We also spoke about where he was from, which was interesting to hear about.  Eventually he asked what I do and I was able to hand him a card at the end of an enjoyable conversation about Scotch and different parts of the country.


  • Going to Meetups has proven to be a successful activity to engage in to find clients looking for freelance developers.  Don’t expect a hit every time, but persistently attending them could be worthwhile and lead to finding people looking for your skills and hiring you.
  • While the Meetups can be fruitful, going to hotel bars or restaurants where a large convention or conference is being held allows for more opportunity to meet established business owners that may need website work.
  • The most effective strategy for opening up a dialogue with people about Web Development and Programming is taking a book with you and making it visible to others nearby.  This is almost without fail a conversation starter and a great way to break the ice and converse with others who may need website work and could use your services.
  • People don’t like being given a pitch and are skeptical if you come out swinging directly trying to sell your services (this, of course, can depend on the context).  People do like having a good conversation (just like you do) and that should be the focus and goal.  You will find a much more receptive attitude towards you when you bring up web development in the course of the conversation.
  • I’ve found that going out ‘into the field’ and meeting people in person is the best way to find clients as a freelance web developer.   Online freelance sites like Upwork, etc. have proven to be ineffective and not fruitful.  Meeting as many people as you can and getting involved in the local scene seems to be the best way.

Other Resources:

How to Get to work with React-Native

After a frustrating couple of days trying to get to connect to a web socket on my Node/Express server for my project HereHere (place based chat app – please check it out if interested), I finally found out how to make it play nice with React-Native (v0.57).  After reading through numerous posts, GitHub issue threads and blog articles, I thought it would be nice to have one place where you can go to get working with your React-Native project, so this is just a quick post on how to get it set up and working.

Key points:

  • You need to use on the react-native client side of things
  • The latest version of 2.2.0 does not work with React-Native currently apparently, so you need to install and use version 2.1.1
  • In development, the connection URL on the client side may need to be set to one of three possible values: http://localhost:<serverPort>,<serverPort>, http://<your IP assigned by your router>:<serverPort>, or if using an Android emulator (I did not need to do this, but it was recommended on a Stack Overflow post, so try it if all else fails)<serverPort>
  • In production, the connection URL on the client side needs to be pointing to the URL where your server is hosted that handles websocket connections:
  • Note: I am using Expo to bootstrap my React-Native project.
  • I am also using a Node.js server built with Express on the back end.


    1. Install v2.1.1 in your react-native client side project.
      $ npm install [email protected]
    2. In your main App.js file or wherever you would rather initialize, import the library:
      import socketIO from '';
    3. Connect to a Web Socket connection to your server by assigning a const to a call to the imported library function and pass in the url to your server (see the possible values in key points above!!) .  Also, pass in some config params, the most important of which is the `transports` key set to `websocket`.
    4. Connect the socket with .connect() and and add a listener to confirm the connection with a console.log.
      Note: initialize socketIO in componentDidMount() in your main App.js file for your React-Native app.  You could extract the code to a module file and call it ‘startSocketIO.js’ to make things cleaner in App.js.

In App.js (your main app component on the client):

import React from 'react';
import { Platform, StatusBar, StyleSheet, View } from 'react-native';
import { startSocketIO } from './services/socketIO';
// ...any other imports needed

export default class App extends React.Component {
  state= {
componentDidMount() { 
  const socket = socketIO('', {      
  transports: ['websocket'], jsonp: false });   
  socket.on('connect', () => { 
    console.log('connected to socket server'); 

// In this case, note that I used the IP address for my machine assigned by the router (in Network Connection details).  This got it working in my development environment.

For further clarity here is an example of some of the code in my Socket IO module where I initialize and use the library.  I export the module to be used in componentDidMount() in my App.js component (top level App component):


import socketIO from '';
...other imports like config file containing Server Url, any secrets, and actions...
// Initialize Socket IO:
const socket = socketIO(config.SERVER_URL, {
  transports: ['websocket'],
  jsonp: false

// export the function to connect and use socket IO:
export const startSocketIO = (store) => {
  socket.on('connect', () => {
    const { userId } = store.getState().user;
  socket.on('disconnect', () => {
    console.log('connection to server lost.');
  socket.on('newMessage', (message) => {
    store.dispatch(storePublicMessages([ message ]));

// In App.js:
import startSocketIO from ...

export default class App extends React.Component {
  componentDidMount() {

  render() {
      return (
        <Provider store={store}>
          <View style={styles.container}>  
            <AppNavigator />


In order to get it going in Production, you need to pass in the domain URL pointing to where you are hosting your server.  In my case I hosted a Node/Express server on Heroku:

In App.js inside componentDidMount():

const socket = socketIO('', {
  transports: ['websocket'], 
  jsonp: false 
socket.on('connect', () => { 
  console.log('connected to socket server'); 

This was mainly about getting things setup to work on the client side with React-Native, but just for completeness sake, here is a quick snippet of my server.js file which sets up an Express server and  Note that on the server side I am using the library (the latest version) and not  Also note, that you need to use the builtin https Node module to create a server which you pass the express app into, and then pass that server to socketIO.

Example Main Server file in back end code of the project:

const http = require('http');
const express = require('express');
const socketIO = require('');
const mongoose = require('mongoose');
const keys = require('./config/keys');

const PORT = process.env.PORT || 5000;

const app = express();
const server = http.createServer(app);

const io = socketIO(server);
io.on('connection', socket => {
  console.log('client connected on websocket');

server.listen(PORT, () => {
  console.log('server started and listening on port ' + PORT);

Hopefully, this will save you some headache and time pouring through Stack Overflow and posts trying to figure out why your socket IO connection is not working in React-Native.  If you have any questions, comments or suggestions feel free to post them in the comments section and I’ll help with whatever I can.


What is clean code?  How do you write clean code?  I will attempt to address these questions in this “In a Nutshell” series blog post.

Clean Code In a Nutshell:

  • The crux of it is: Clean code is code that is written in a way that is easily understandable, readable and maintainable by other Humans.  It is based on empathy and consideration for other readers of your code.
  • Key aspects of clean code:
  1. It is DRY (does not repeat unnecessarily, more specifically: a piece of knowledge in the application is only in one place).
  2. Concise (but not too concise).
  3. Tells a story to the reader in a logical sequence about what is happening in the code.  The code expresses the author’s intent clearly and is easy to understand and read.
  4. Incorporates the SRP principle, Separation of Concerns and is de-coupled.  This makes it easily maintainable (modular).  Whenever a change must be made, changes to one module will not unexpectedly break or change another module’s functionality.
  5. It does not make the person who is reading and working on it want to pull their hair out.

That’s basically what it means to write clean code.  For further explanation and examples, continue reading…


  • Code Smell: This refers to code that is not clean and “smells”.  There is something about the code that is unnecessarily confusing, unclear or something that could be improved upon to make it cleaner.
  • DRY: The “Don’t Repeat Yourself” principle.  The goal is to eliminate as much repetition of code as possible and re-use as much code as possible, which keeps things less cluttered and clean.  Just keep in mind this doesn’t necessarily mean you should literally never duplicate any code – the most important principle is that specific knowledge is only in one place.
  • SRP: The “Single Responsibility” principle.  A method, object or class should do one thing and do it very well.  This makes things easier to maintain and keeps things de-coupled.  Another way to put it is that a module should have only one reason to change (or alternatively, only one agent/person/department should be interested in asking it to change).
  • Separation Of Concerns: Code should be logically grouped based on what operations it is performing or data it is related to.  Methods in classes and objects should be closely related to each other and separated.
  • Spaghetti Code: This is code that is not separated logically and is not modular.  It is very difficult to maintain and understand and is usually heavily coupled (the code is connected to and affects many other parts and operations in the application) so that changing one line may have unforeseen side effects on all sorts of other parts of the code base.  It is a nightmare to deal with and the opposite of modular, de-coupled clean code.

METHODS FOR WRITING CLEAN CODE (and what to avoid doing):



  • Names for variables should not be ambiguous, but be descriptive and meaningful.
  • Magic Numbers and Magic Strings: Strings or Numbers that have some meaning that is not obvious to the reader of the code.  Always label these values with a descriptive variable name.  Name them so that it’s clear to the reader what those variables should be used for or what they represent.
  • Make naming formats consistent.  If related variables are in camel case, then make sure all of them are in camel case, or if there is a word that is appended or prepended to related variables, make sure you keep using that format.
  • Follow naming conventions in whatever language or framework you are using.  For example, in C# the names of classes, it’s properties and methods use Pascal Case while private fields, method parameters(arguments) and local variables use Camel Case.  In React, all component names and classes should have the first letter capitalized.


// Avoid: Ambiguous variable names:
const x = "Please log in.";
// Cleaner:
const loginMessage = "Please log in.";

// Avoid: Magic Numbers and Strings:
if (userStatus === 1) {
  return true;
// Cleaner:
const LOGGED_IN_STATUS = 1; 
if (userStatus === LOGGED_IN_STATUS) {
  return true;

// Avoid: inconsistent naming formats:
const username = "Brent";
const user_profile = {...};
// Cleaner:
const userName = "Brent";
const userProfile = {...};

METHODS, OBJECTS, CLASSES (SRP and Separation of Concerns):

  • Keep methods short and have them do only one thing!  Methods should do one thing and do it very well (have a single responsibility; They should only have one reason to change!  A method should not contain both view layer logic and persistence layer logic, for example.).  There are differing opinions on how long methods should be, but the goal is to make them as short and as contained as possible.  Some say they should not be more than 10 lines of code.   Just try to make them as short as possible without making the code hard to understand.
  • If methods get too long, look for ways to extract parts of the code to a separate object or function that you use inside the method.
  • Use Abstraction.  Abstract away the complexity of a complicated piece code by placing it in it’s own function with a descriptive variable name.  Create Black Boxes that can be used simply without needing to know their inner workings and implementation details.
  • Cohesion Principle: All code that is related closely should be together and code that is not should be separated.  Objects and classes should contain data and methods that relate to each other and only have to do with the data or operation that the object/class is concerned with (Separation of Concerns).
  • The Information Expert Principle (or similarly the Encapsulation principle) states that a method should be attached to the class or object that has the related data which it uses (similar to the Cohesion Principle).
  • The number of parameters should not exceed three.   You can extract parameters and batch them in objects or encapsulate them to reduce the number of arguments.  If possible, also avoid using booleans as parameters.
  • Variables should be declared as close as possible to where they are being used so the reader does not have to search around to find where they are defined.


// Avoid: long methods and functions with too many parameters and that have more than one reason to change.
function fetchUserDataAndLoginUser(loggedIn, userId, db, userProfile, userPassword) {
  let userData = {};
  if (!user.loggedIn) {
     lots of code to login user on the back end
     userData = db.find({ user: });
     lots of code to load userData into a profile view
  } else {

// Cleaner (separate the operations into separate functions to use and combine the arguments into a single encapsulated object):

const user = {

function authenticateUser(user) {
  ...code for checking passwords from input and authenticating the user.

function fetchUserData(userId) {
  ...code for fetching authenticated user data from the database

function renderUserData(userData) {
  ...code to render user data on the view.

  .then(user => {
  .then(userData => {

// Note how the code now tells a story in a logical succession to the reader and each method only has one responsibility.  The user is logged in and authenticated (auth layer), then their data is fetched from the database (persistence layer), and then the data is rendered to the view (view layer).  
// The code is also easier to maintain because if there is a point of failure along the way, the developer can look in the appropriate method dealing with the failing operation and pinpoint and debug quickly and easily which step is failing.


  • Keep conditional statements as flat and easy to understand as possible by using refactoring techniques and avoiding common pitfalls.
  • A Nested Conditional is an if/else statement contained within another if/else statement.  Avoid them: they are difficult to read, understand and test.
  • There are circumstances in which nested conditionals can be refactored by considering the relationship of the conditional parameters between the statements.  (See examples below).

Examples and ways of refactoring conditionals:

Use a Ternary Operator:

// Not nested, but easily refactored to make the code cleaner and more readable:
  if ( {
    userLoggedIn = true;
  } else {
    userLoggedIn = false;
// Cleaner:
userLoggedIn = ? true : false;

// NOTE: Never combine ternary expressions and only use one - multiple ternary expressions are too difficult to understand and read.

If the statement sets a Boolean to a variable based on the condition, you can simplify it by setting the variable to the condition in a single line eliminating the if/else statement:

// Avoid: Unnecessary if/else statement:
if (userLoggedIn) {
  showProfile = true;
} else {
  showProfile = false;
// Cleaner: showProfile is simply dependent on the value in the can be refactored to:
showProfile = userLoggedIn; 

Watch out for nested if statements that a single block is based on. Use the && operator or early exit technique to refactor:

// Avoid: nested if statement inside a single block:
if (user) {
  if ( {
// Cleaner: refactor using the && operator to eliminate the nested if statement:
if (user && { ...code } 

// Or use the early exit technique to return if a or b is not true to eliminate nesting:
function verifyUser(user) {
  if (!user || ! {
  ...code to run if user and are true

Refactor nested if statements that have repeated conditions to combine them with logical operators:

// Avoid: Nested conditionals with identical conditions:
if (userIsGoldMember) {
  if(itemsInCart) {
    applyDiscount = true;
if (totalCost > 100) {
  if (itemsInCart) {
    applyDiscount = true;
// Cleaner: combine the identical conditions and variant conditions with logical operators to eliminate the nested conditional:
applyDiscount = (itemsInCart && (totalCost > 100 || userIsGoldMember));

Make conditions more expressive by extracting the condition to return a boolean in a separate function:

// Avoid: conditions that are complicated and not easy to read quickly:
if ( >= (new Date(2018, 11, 25).getTime() + 24 * 60 * 60 * 1000)) {

// Cleaner: separate the condition into a function that returns true or false and describes what the condition is clearly:
const isNowLaterThan24HoursAfter = (datetime) => {
  const twentyFourHours = 24 * 60 * 60 * 1000;
  return ( >= (datetime + twentyFourHours))

const deadline = new Date(2018, 11, 25).getTime();

if (isNowLaterThan24HoursAfter(deadline)) {

Refactor Triplicate conditionals: A triplicate occurs when one of multiple conditions must be true.  Use the || operator instead of if statements:

// Avoid: separate if conditionals for a triplicate:
if (condition1) {
  return true;
if (condition2) {
  return true;
if (condition3) {
  return true;
return false;

// Cleaner: use the || operator:
return condition1 || condition2 || condition3;


  • Comments should not be redundant or stating what is obvious in the code – it just makes more clutter in the code base.
  • Ideally, you shouldn’t need comments since the code you are writing tells a story and it is clear and easy to understand what is going on.  Code should be self documenting for the most part. There are differing opinions on this and I think it is a good rule of thumb to follow most of the time.  In reality, there may be a situation where comments would be very helpful to the reader (i.e. when the code is written in an unintuitive way that is necessary for some reason or refactoring would cause breaking changes).
  • If you need to write a comment, don’t write comments addressing “whats” (what the code is ‘saying’, so to speak), write comments that address “whys” and “hows” (why this code is the way it is or how it is doing some operation).
  • Minimizing comments is also a good idea because developers may change logic in the code base, but not update the comments which can cause confusion in the future.
  • The exception to leaving out comments is the case of making a `TODO` note;  These are good comments when a problem in the code is found and a note needs to be left for an issue that needs to be fixed and addressed.  Ideally, however, TODOs should be fixed on the spot.
  • If you see comments for blocks of code describing operations in a method, that is a sign that it should be refactored and the operations should be extracted into separate methods.


Comments Explorer Overview

I’m excited to have just deployed a working demo of a React app I’ve been developing which allows users to pull comments from Youtube videos and search them by text/author/date/like count.

Click HERE for the Prototype Full Version.

How to use the app:

Just find a Youtube video you have an interest in exploring the comments section of, and copy and paste the URL into the form field on the landing page.  Click the Display Comments button and you’ll be presented with a list you can search through using the filters at the top of the page.  If you want to reply to or edit and delete your own comments, just log in with Google by clicking the button in the top header of the page.

The reason for the App:

I was always frustrated when reading through comments on Youtube videos to see if a question I had about the video topic had been addressed by somebody already.  Since the comments are loaded as you scroll down the page, you can’t just hit CTRL-F and search for what you’re looking for without sitting there for a long time scrolling and scrolling.  With this app, pages of comments are downloaded and searchable by text, date, author and sort-able by like count and newest/oldest making it easy to search for comments that match what you’re trying to find.

Use Cases:

  • Social Media Managing Tool: Monitoring and exploring comments for videos in an easy to use GUI (Graphical User Interface) for small businesses or content creators.
  • Researching a particular topic of interest and seeing what other people  have to say about it in the comments section.  For instance, if someone is interested in a phone or laptop, they could search the comments for “battery” or “good tips”, etc.  This could be for any topic such as a video on a hiking trail, investment related videos and whatever else you can find on Youtube that is of interest.  The possibilities for research and discovery by scouring the depths of the comments section was the original inspiration for creating this app.
  • Finding new ideas for insults or names to hurl at other people.  The comments section can be a very useful resource for this.

Main Features:

  • Search a list of comments for a video by text (phrase or keyword) or by author. 
  • Respond and reply to comments found in search results through the app Interface easily by logging in with Google.
  • Delete or edit comments of which you are the author after logging in with Google.
  • Sort comments by date, like count, thread or spread out individually.

Features in the full version:

  • Dashboard for managing saved comments and previous searches with the ability to organize saved items in category folders (i.e. you could have a “Customer Complaints” folder or a “Response Complete” folder for managing comments posted by customers and viewers).
  • Retrieve up to 40 pages of comments (would equate to potentially 4,000+ comments) for searching, exploring and managing comments on videos of interest.  The demo currently only allows a search limit of 5 pages of comments (~500 comments).
  • Easily Mark comments as spam or remove them from your video comments page as a content creator.

Example of a list of comments on the results page with search filters, etc:

Tools and Technologies Used:

The app consumes the Youtube DATA REST API and is currently awaiting approval for commercialization from Youtube.  It was built with React using Redux, Node.js with Express, PostgreSQL using Knex.js as the ORM.

I hope you find this app useful and fun to use and I would love to hear any feedback or suggestions from anyone who uses the app.