Python vs JavaScript for Pythonistas

Python vs JavaScript for Pythonistas

Watch Now This tutorial has a related video course created by the Real Python team. Watch it together with the written tutorial to deepen your understanding: Python vs JavaScript for Python Developers

If you’re serious about web development, then you’ll need to learn about JavaScript at some point. Year after year, numerous surveys have shown that JavaScript is one of the most popular programming languages in the world, with a large and growing community of developers. Just like Python, modern JavaScript can be used almost anywhere, including the front end, back end, desktop, mobile, and the Internet of Things (IoT). Sometimes it might not be an obvious choice between Python vs JavaScript.

If you’ve never used JavaScript before or have felt overwhelmed by the quick pace of its evolution in recent years, then this article will set you on the right path. You should already know the basics of Python to benefit fully from the comparisons made between the two languages.

In this article, you’ll learn how to:

  • Compare Python vs JavaScript
  • Choose the right language for the job
  • Write a shell script in JavaScript
  • Generate dynamic content on a web page
  • Take advantage of the JavaScript ecosystem
  • Avoid common pitfalls in JavaScript

JavaScript at a Glance

If you’re already familiar with the origins of JavaScript or just want to see the code in action, then feel free to jump ahead to the next section. Otherwise, prepare for a brief history lesson that will take you through the evolution of JavaScript.

It’s Not Java!

Many people, notably some IT recruiters, believe that JavaScript and Java are the same language. It’s hard to blame them, though, because inventing such a familiar-sounding name was a marketing trick.

JavaScript was originally called Mocha before it was renamed to LiveScript and finally rebranded as JavaScript shortly before its release. At the time, Java was a promising web technology, but it was too difficult for nontechnical webmasters. JavaScript was intended as a somewhat similar but beginner-friendly language to supplement Java applets in web browsers.

To add to the confusion, Microsoft developed its own version of the language, which it called JScript due to a lack of licensing rights, for use with Internet Explorer 3.0. Today, people often refer to JavaScript as JS.

While Java and JavaScript share a few similarities in their C-like syntax as well as in their standard libraries, they’re used for different purposes. Java diverged from the client side into a more general-purpose language. JavaScript, despite its simplicity, was sufficient for validating HTML forms and adding little animations.

It’s ECMAScript

JavaScript was developed in the early days of the Web by a relatively small company known as Netscape. To win the market against Microsoft and mitigate the differences across web browsers, Netscape needed to standardize their language. After being turned down by the international World Wide Web Consortium (W3C), they asked a European standardization body called ECMA (today Ecma International) for help.

ECMA defined a formal specification for the language called ECMAScript because the name JavaScript had been trademarked by Sun Microsystems. JavaScript became one of the implementations of the specification that it originally inspired.

While individual implementations of the specification complied with ECMAScript to some extent, they also shipped with additional proprietary APIs. This led to web pages not displaying correctly across different browsers and the advent of libraries such as jQuery.

Are There Other Scripts?

To this day, JavaScript remains the only programming language natively supported by web browsers. It’s the lingua franca of the Web. Some people love it, while others don’t.

There have been—and continue to be—many attempts to replace or supplant JavaScript with other technologies, including:

  • Rich Internet Applications: Flash, Silverlight, JavaFX
  • Transpilers: Haxe, Google Web Toolkit, pyjs
  • JavaScript dialects: CoffeeScript, TypeScript

These attempts were driven not only by personal preference but also by web browsers’ limitations before HTML5 came onto the scene. In those days, you couldn’t use JavaScript for computationally intensive tasks such as drawing vector graphics or processing audio.

Rich Internet Applications (RIA), on the other hand, offered an immersive desktop-like experience in the browser through plugins. They were great for games and processing media. Unfortunately, most of them were closed source. Some had security vulnerabilities or performance issues on certain platforms. To top it off, they all severely limited the ability of web search engines to index pages built with these plugins.

Around the same time came transpilers, which allowed for an automated translation of other languages into JavaScript. This made the entry barrier to front-end development much lower because suddenly back-end engineers could leverage their skills in a new field. However, the downsides were slower development time, limited support for web standards, and cumbersome debugging of the transpiled JavaScript code. To link it back to the original code, you’d need a source map.

To write Python code for the browser, you can use one of the available transpilers, such as Transcrypt or pyjs. The latter is a port of Google Web Toolkit (GWT), which was a wildly popular Java-to-JavaScript transpiler. Another option is to use a tool like Brython, which runs a streamlined version of the Python interpreter in pure JavaScript. However, the benefits might be offset by poor performance and lack of compatibility.

Transpiling allowed a ton of new languages to emerge with the intent of replacing JavaScript and addressing its shortcomings. Some of these languages were closely related dialects of JavaScript. Perhaps the first was CoffeeScript, which was created about a decade ago. One of the latest was Google’s Dart, which was the fastest-growing language in 2019 according to GitHub. Many more languages followed, but most of them are now obsolete due to the recent advances in JavaScript.

One glaring exception is Microsoft’s TypeScript, which has gained much popularity in recent years. It’s a fully compatible superset of JavaScript that adds optional static type checking. If that sounds familiar to you, that’s because Python’s type hinting was inspired by TypeScript.

Search Interest in TypeScript According to Google Trends
Search Interest in TypeScript According to Google Trends

While modern JavaScript is mature and actively developed, transpiling is still a common approach to ensure backward compatibility with older browsers. Even if you’re not using TypeScript, which seems to be the language of choice for many new projects, you’re still going to need to transpile your shiny new JavaScript into an older version of the language. Otherwise, you run the risk of getting a runtime error.

Some transpilers also synthesize cutting-edge web APIs, which might be unavailable on certain browsers, with a so-called polyfill.

Today, JavaScript can be thought of as the assembly language of the Web. Many professional front-end engineers tend not to write it by hand anymore. In such a case, it’s generated from scratch through transpiling.

However, even handwritten code often gets processed in some way. For example, minification removes whitespace and renames variables to reduce the amount of data to transfer and to obfuscate the code so that it’s harder to reverse engineer. This is analogous to compiling the source code of a high-level programming language into native machine code.

In addition to this, it’s worthwhile to mention that contemporary browsers support the WebAssembly standard, which is a fairly new technology. It defines a binary format for code that can run with almost-native performance in the browser. It’s fast, portable, secure, and allows for cross compilation of code written in languages like C++ or Rust. With it, for example, you could take the decades-old code of your favorite video game and run it in the browser.

At the moment, WebAssembly helps you optimize the performance of computationally critical parts of your code, but it comes with a price tag. To begin with, you need to know one of the currently supported programming languages. You have to become familiar with low-level concepts such as memory management as there’s no garbage collector yet. The integration with JavaScript code is difficult and costly. Also, there’s no easy way to call web APIs from it.

It seems that, after all these years, JavaScript isn’t going away anytime soon.

JavaScript Starter Kit

One of the first similarities you’ll notice when comparing Python vs JavaScript is that the entry barriers for both are pretty low, making both languages very attractive to beginners who’d like to learn to code. For JavaScript, the only starting requirement is having a web browser. If you’re reading this, then you’ve already got that covered. This accessibility contributes to the language’s popularity.

The Address Bar

To get a taste of what it’s like to write JavaScript code, you can stop reading now and type the following text into the address bar before navigating to it:

JavaScript in the Address Bar

The literal text is javascript:alert('hello world'), but don’t just copy and paste it!

That part after the javascript: prefix is a piece of JavaScript code. When confirmed, it should make your browser display a dialog box with the hello world message in it. Each browser renders this dialog slightly differently. For example, Google Chrome displays it like this:

Alert Dialog Box in JavaScript

Copying and pasting such a snippet into the address bar will fail in most browsers, which filter out the javascript: prefix as a safety measure against injecting malicious code.

Some browsers, such as Mozilla Firefox, take it one step further by blocking this kind of code execution entirely. In any case, this isn’t the most convenient way of working with JavaScript because you’re constrained to only one line and limited to a certain number of characters. There’s a better way.

Web Developer Tools

If you’re viewing this page on a desktop or a laptop computer, then you can take advantage of the web developer tools, which provide comparable experience across competing web browsers.

To toggle these tools, refer to your browser’s documentation or try one of these common keyboard shortcuts:

  • F12

  • Ctrl+Shift+I

  • Cmd+Option+I

This feature may be disabled by default if you’re using Apple Safari or Microsoft Edge, for example. Once the web developer tools are activated, you’ll see a myriad of tabs and toolbars with content similar to this:

Web Developer Tools in Google Chrome
Web Developer Tools in Google Chrome

Collectively, it’s a powerful development environment equipped with a JavaScript debugger, a performance and memory profiler, a network traffic manager, and much, much more. There’s even a remote debugger for physical devices connected over a USB cable!

For the moment, however, just focus on the console, which you can access by clicking a tab located at the top. Alternatively, you can quickly bring it to the front by pressing Esc at any time while using the web developer tools.

The console is primarily used for inspecting log messages emitted by the current web page, but it can also be a great JavaScript learning aid. Just like with the interactive Python interpreter, you can type JavaScript code directly into the console to have it executed on the fly:

JavaScript Console in Web Developer Tools
JavaScript Console in Web Developer Tools

It has everything you’d expect from a typical REPL tool and more. In particular, the console comes with syntax highlighting, contextual autocomplete, command history, line editing similar to GNU Readline, and the ability to render interactive elements. Its rendering abilities can be especially useful for introspecting objects and tabular data, jumping to source code from a stack trace, or viewing HTML elements.

You can log custom messages to the console using a predefined console object. JavaScript’s console.log() is the equivalent of Python’s print():

console.log('hello world');

This will make the message appear in the console tab in the web developer tools. Apart from that, there are a few more useful methods available in the console object.

HTML Document

By far the most natural place for the JavaScript code is somewhere near an HTML document, which it typically manipulates. You’ll learn more on that later. You can reference JavaScript from HTML in three different ways:

Method Code Example
HTML Element’s Attribute <button onclick="alert('hello');">Click</button>
HTML <script> Tag <script>alert('hello');</script>
External File <script src="/path/to/file.js"></script>

You can have as many of these as you like. The first and second methods embed inline JavaScript directly within an HTML document. While this is convenient, you should try to keep imperative JavaScript separate from declarative HTML to promote readability.

It’s more common to find one or more <script> tags referencing external files with JavaScript code. These files can be served by either a local or a remote web server.

The <script> tag can appear anywhere in the document as long as it’s nested in either the <head> or the <body> tag:

<!DOCTYPE html>
  <meta charset="UTF-8">
  <title>Home Page</title>
  <script src=""></script>
  <script src="local/assets/app.js"></script>
    function add(a, b) {
      return a + b;
  <p>Lorem ipsum dolor sit amet (...)</p>
    console.log(add(2, 3));

What’s important is how web browsers process HTML documents. A document is read top to bottom. Whenever a <script> tag is found, it gets immediately executed even before the page has been fully loaded. If your script tries to find HTML elements that haven’t been rendered yet, then you’ll get an error.

To be safe, always put the <script> tags at the bottom of your document body:

<!DOCTYPE html>
  <meta charset="UTF-8">
  <title>Home Page</title>
  <p>Lorem ipsum dolor sit amet (...)</p>
  <script src=""></script>
  <script src="local/assets/app.js"></script>
    function add(a, b) {
      return a + b;
    console.log(add(2, 3));

Not only will this protect you against the said error, but it will also improve the overall user experience. By moving those tags down, you’re allowing the user to see the fully rendered page before the JavaScript files start to download. You could also defer the download of external JavaScript files until the page has loaded:

<script src="" defer></script>

If you want to find out more about mixing JavaScript with HTML, then take a look at a JavaScript Tutorial by W3Schools.


You don’t need a web browser to execute JavaScript code anymore. There’s a tool called Node.js that provides a runtime environment for server-side JavaScript.

A runtime environment comprises the JavaScript engine, which is the language interpreter or compiler, as well as an API for interacting with the world. There are several alternative engines that come with different web browsers:

Web Browser JavaScript Engine
Apple Safari JavaScriptCore
Microsoft Edge V8
Microsoft IE Chakra
Mozilla Firefox SpiderMonkey
Google Chrome V8

Each of these is implemented and maintained by its vendor. For the end user, however, there’s no noticeable difference except for the performance of individual engines. Node.js uses the same V8 engine developed by Google for its Chrome browser.

When running JavaScript inside a web browser, you typically want to be able to respond to mouse clicks, dynamically add HTML elements, or maybe get an image from the webcam. But that doesn’t make sense in a Node.js application, which runs outside of the browser.

After you’ve installed Node.js for your platform, you can execute JavaScript code just like with the Python interpreter. To start an interactive session, go to your terminal and type node:

$ node
> 2 + 2

This is similar to the web developer console that you saw earlier. However, as soon as you try to refer to something browser related, you’ll get an error:

> alert('hello world');
ReferenceError: alert is not defined

That’s because your runtime environment is missing the other component, which is the browser API. At the same time, Node.js provides a set of APIs that are useful in a back-end application, such as the file system API:

> const fs = require('fs');
> fs.existsSync('/path/to/file');

For safety reasons, you won’t find these APIs in the browser. Imagine allowing some random website to have control over the files on your computer!

If the standard library doesn’t satisfy your needs, then you can always install a third-party package with the Node Package Manager (npm) that comes with the Node.js environment. To browse or search for packages, go to the npm public registry, which is like the Python Package Index (PyPI).

Similar to the python command, you can run scripts with Node.js:

$ echo "console.log('hello world');" > hello.js
$ node hello.js
hello world

By providing a path to a text file with the JavaScript code inside, you’re instructing Node.js to run that file instead of starting a new interactive session.

On Unix-like systems, you can even indicate which program to run the file with using a shebang comment in the very first line of the file:

#!/usr/bin/env node
console.log('hello world');

The comment has to be a path to the Node.js executable. However, to avoid hard-coding an absolute path, which may differ across installations, it’s best to let the env tool figure out where Node.js is installed on your machine.

Then you have to make the file executable before you can run it as if it were a Python script:

$ chmod +x hello.js
$ ./hello.js
hello world

The road to building full-blown web applications with Node.js is long and winding, but so is the path to writing Django or Flask applications in Python.

Foreign Language

Sometimes the runtime environment for JavaScript can be another programming language. This is typical of scripting languages in general. Python, for example, is widely used in plugin development. You’ll find it in the Sublime Text editor, GIMP, and Blender.

To give you an example, you can evaluate JavaScript code in a Java program using the scripting API:

package org.example;

import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;

public class App {

    public static void main(String[] args) throws ScriptException {

        final ScriptEngineManager manager = new ScriptEngineManager();
        final ScriptEngine engine = manager.getEngineByName("javascript");

        System.out.println(engine.eval("2 + 2"));

This is a Java extension, though it might not be available in your particular Java virtual machine. Subsequent Java generations bundle alternative scripting engines, such as Rhino, Nashorn, and GraalVM.

Why is this useful?

As long as the performance isn’t too bad, you could reuse the code of an existing JavaScript library instead of rewriting it in another language. Perhaps solving a problem, such as math expression evaluation, would be more convenient with JavaScript than your native language. Finally, using a scripting language for behavior customization at runtime, like data filtering or validation, could be the only way to go in a compiled language.

JavaScript vs Python

In this section, you’ll compare Python vs JavaScript from a Pythonista’s perspective. There will be some new concepts ahead, but you’ll also discover a few similarities between the two languages.

Use Cases

Python is a general-purpose, multi-paradigm, high-level, cross-platform, interpreted programming language with a rich standard library and an approachable syntax.

As such, it’s used across a wide range of disciplines, including computer science education, scripting and automation, prototyping, software testing, web development, programming embedded devices, and scientific computing. Although it’s doable, you probably wouldn’t choose Python as the primary technology for video game or mobile app development.

JavaScript, on the other hand, originated solely as a client-side scripting language for making HTML documents a little more interactive. It’s intentionally simple and has a singular focus: adding behavior to user interfaces. This is still true today despite its improved capabilities. With Javascript, you can build not only web applications but also desktop programs and mobile apps. Tailor-made runtime environments let you execute JavaScript on the server or even on IoT devices.


Python emphasizes code readability and maintainability at the price of its expressiveness. After all, you can’t even format your code too much without breaking it. You also won’t find esoteric operators like you would in C++ or Perl since most of the Python operators are English words. Some people joke that Python is executable pseudocode thanks to its straightforward syntax.

As you’ll find out later, JavaScript offers much more flexibility but also more ways to cause trouble. For example, there’s no one right way of creating custom data types in JavaScript. Besides, the language needs to remain backward compatible with older browsers even when new syntax fixes a problem.


Up until recently, you would find two largely incompatible versions of Python available for download on its official website. This divide between Python 2.7 and Python 3.x was confusing to beginners and was a major factor in slowing down the adoption of the latest development branch.

In January 2020, after years of delaying the deadline, the support for Python 2.7 was finally dropped. However, despite the looming lack of security updates and warnings issued by some government agencies, there are still a lot of projects that haven’t migrated yet:

Timeline of JavaScript and Python Versions
Timeline of JavaScript and Python Versions

Brendan Eich created JavaScript in 1995, but the ECMAScript we know today was standardized two years later. Since then, there have been only a handful of releases, which looks stagnant compared to the multiple new versions of Python released each year during the same period.

Notice the gap between ES3 and ES5, which lasted an entire decade! Due to political conflicts and disagreements in the technical committee, ES4 never made its way to web browsers, but it was used by Macromedia (later Adobe) as a base for ActionScript.

The first major overhaul to JavaScript came in 2015 with the introduction of ES6, also known as ES2015 or ECMAScript Harmony. It brought a lot of new syntactical constructs, which made the language more mature, safe, and convenient for the programmer. It also marked a turning point in the ECMAScript release schedule, which now promises a new version every year.

Such a fast pace means that you can’t assume the latest language version has been adopted by all major web browsers since it takes time to roll out updates. That’s why transpiling and polyfills prevail. Today, pretty much any modern web browser can support ES5, which is the default target for the transpilers.


To run a Python program, you first need to download, install, and possibly configure its interpreter for your platform. Some operating systems provide an interpreter out of the box, but it may not be the version that you’re looking to use. There are alternative Python implementations, including CPython, PyPy, Jython, IronPython, or Stackless Python. You can also choose from multiple Python distributions, such as Anaconda, that come with preinstalled third-party packages.

JavaScript is different. There’s no stand-alone program to download. Instead, every major web browser ships with some kind of JavaScript engine and an API, which together make the runtime environment. In the previous section, you learned about Node.js, which allows for running JavaScript code outside of the browser. You also know about the possibility to embed JavaScript in other programming languages.


A language ecosystem consists of its runtime environment, frameworks, libraries, tools, and dialects as well as its best practices and unwritten rules. Which combination you choose will depend on your particular use case.

In the old days, you didn’t need much more than a good code editor to write JavaScript. You’d download a few libraries like jQuery, Underscore.js, or Backbone.js, or rely on a Content Delivery Network (CDN) to provide them for your clients. Today, the number of questions you need to answer and the tools you need to acquire to start building even the simplest website can be daunting.

The build process for a front-end app is as complicated as it is for a back-end app, if not more so. Your web project goes through linting, transpilation, polyfilling, bundling, minification, and more. Heck, even the CSS style sheets are no longer sufficient and need to be compiled from an extension language by a preprocessor such as Sass or Less.

To alleviate that, some frameworks offer utilities that set up the default project structure, generate configuration files, and download dependencies for you. As an example, you can create a new React app with this short command, provided that you already have the latest Node.js on your computer:

$ npx create-react-app todo

At the time of writing, this command took several minutes to finish and installed a whopping 166 MB in 1,815 packages! Compare this to starting a Django project in Python, which is instantaneous:

$ django-admin startproject blog

The modern JavaScript ecosystem is enormous and keeps evolving, which makes it impossible to give a thorough overview of its elements. You’ll encounter plenty of foreign tools as you’re learning JavaScript. However, the concepts behind some of them will sound familiar. Here’s how you can map them back to Python:

Python JavaScript
Code Editor / IDE PyCharm, VS Code Atom, VS Code, WebStorm
Code Formatter black Prettier
Dependency Manager Pipenv, poetry bower (deprecated), npm, yarn
Documentation Tool Sphinx JSDoc, sphinx-js
Interpreter bpython, ipython, python node
Library requests, dateutil axios, moment
Linter flake8, pyflakes, pylint eslint, tslint
Package Manager pip, twine bower (deprecated), npm, yarn
Package Registry PyPI npm
Package Runner pipx npx
Runtime Manager pyenv nvm
Scaffolding Tool cookiecutter cookiecutter, Yeoman
Test Framework doctest, nose, pytest Jasmine, Jest, Mocha
Web Framework Django, Flask, Tornado Angular, React, Vue.js

This list isn’t exhaustive. Besides, some of the tools mentioned above have overlapping capabilities, so it’s hard to make an apples-to-apples comparison in each category.

Sometimes there isn’t a direct analogy between Python vs JavaScript. For example, while you may be used to creating isolated virtual environments for your Python projects, Node.js handles that out of the box by installing dependencies into a local folder.

Conversely, JavaScript projects may require additional tools that are unique to front-end development. One such tool is Babel, which transpiles your code according to various plugins grouped into presets. It can handle experimental ECMAScript features as well as TypeScript and even React’s JSX extension syntax.

Another category of tool is the module bundler, whose role is to consolidate multiple independent source files into one that can be easily consumed by a web browser.

During development, you want to break down your code into reusable, testable, and self-contained modules. That’s reasonable for an experienced Python programmer. Unfortunately, JavaScript didn’t originally come with support for modularity. You still need to use a separate tool for that, although this requirement is changing. Popular choices for module bundlers are webpack, Parcel, and Browserify, which can also handle static assets.

Then you have build automation tools such as Grunt and gulp. They are vaguely similar to Fabric and Ansible in Python, although they’re used locally. These tools automate boring tasks such as copying files or running the transpiler.

In a large-scale single-page application (SPA) with a lot of interactive UI elements, you may need a specialized library such as Redux or MobX for state management. These libraries aren’t tied to any particular front-end framework but can be quickly hooked up.

As you can see, learning the JavaScript ecosystem is an endless journey.

Memory Model

Both languages take advantage of automatic heap memory management to eliminate human error and to reduce cognitive load. Nevertheless, this doesn’t completely free you from the risk of getting a memory leak, and it adds some performance overhead.

The orthodox CPython implementation uses reference counting as well as non-deterministic garbage collection (GC) to deal with reference cycles. Occasionally, you may be forced to manually allocate and reclaim the memory when you venture into writing a custom C extension module.

In JavaScript, the actual implementation of memory management is also left to your particular engine and version since it’s not a part of the language specification. The basic strategy for garbage collection is usually the mark-and-sweep algorithm, but various optimization techniques exist.

For example, the heap can be organized into generations that separate short-lived objects from long-lived ones. Garbage collection can run concurrently to offload the main thread of execution. Taking an incremental approach can help avoid bringing the program to a complete stop while the memory is cleaned up.

JavaScript Type System

You must be itching to learn about the JavaScript syntax, but first let’s take a quick look at its type system. It’s one of the most important components that define any programming language.

Type Checking

Both Python and JavaScript are dynamically typed because they check types at runtime, when the application is executing, rather than at compile time. It’s convenient because you aren’t forced to declare a variable’s type such as int or str:

>>> data = 42
>>> data = 'This is a string'

Here, you reuse the same variable name for two different kinds of entities that have distinct representations in computer memory. First it’s an integer number, and then it’s a piece of text.

Dynamic typing is often misunderstood as not having any types whatsoever. This is coming from languages in which a variable works like a box that can only fit a certain type of object. In both Python and JavaScript, the type information is tied not to the variable but to the object it points to. Such a variable is merely an alias, a label, or a pointer to some object in memory.

A lack of type declarations is great for prototyping, but in larger projects it quickly becomes a bottleneck from the maintenance point of view. Dynamic typing is less secure due to a higher risk of bugs going undetected inside of infrequently exercised code execution paths.

Moreover, it makes reasoning about the code much more difficult both for humans and for code editors. Python addressed this problem by introducing type hinting, which you can sprinkle variables with:

data: str = 'This is a string'

By default, type hints provide only informative value since the Python interpreter doesn’t care about them at runtime. However, you can add a separate utility, such as a static type checker, to your tool chain to get an early warning about mismatched types. The type hints are completely optional, which makes it possible to combine dynamically typed code with statically typed code. This approach is known as gradual typing.

The idea of gradual typing was borrowed from TypeScript, which is essentially JavaScript with types that you can transpile back to plain old JavaScript.

Another common feature of both languages is the use of duck typing for testing type compatibility. However, an area where Python vs JavaScript are significantly different is the strength of their type-checking mechanisms.

Python demonstrates strong typing by refusing to act upon objects with incompatible types. For example, you can use the plus (+) operator to add numbers or to concatenate strings, but you can’t mix the two:

>>> '3' + 2
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can only concatenate str (not "int") to str

The interpreter won’t implicitly promote one type to another. You have to decide for yourself and make a suitable type casting manually. If you wanted an algebraic sum, then you’d do this:

>>> int('3') + 2

To join the two strings together, you’d cast the second operand accordingly:

>>> '3' + str(2)
>>> '32'

JavaScript, on the other hand, uses weak typing, which automatically coerces types according to a set of rules. Unfortunately, these rules are inconsistent and hard to remember as they depend on operator precedence.

Taking the same example as before, JavaScript will implicitly convert numbers to strings when you use the plus (+) operator:

> '3' + 2

That’s great as long as it’s the desired behavior. Otherwise, you’ll be pulling your hair out trying to find the root cause of a logical error. But it gets even more funky than that. Let’s see what happens if you change the operator to something else:

> '3' - 2

Now it’s the other operand that gets converted to a number so the end result isn’t a string. As you can see, weak typing can be quite surprising.

The strength of type checking isn’t just black and white. Python lies somewhere in the middle of this spectrum. For instance, it’ll happily add an integer to a floating-point number, whereas the Swift programming language would raise an error in such a situation.

To recap, JavaScript is dynamically as well as weakly typed and supports duck typing.

JavaScript Types

In Python, everything is an object, whereas JavaScript makes a distinction between primitive and reference types. They differ in a couple of ways.

First, there are only a few predefined primitive types that you need to care about because you can’t make your own. The majority of built-in data types that come with JavaScript are reference types.

These are the only primitive types available in JavaScript:

  • boolean
  • null
  • number
  • string
  • symbol (since ES6)
  • undefined

On the other hand, here are a handful of reference types that come with JavaScript off the shelf:

  • Array
  • Boolean
  • Date
  • Map
  • Number
  • Object
  • RegExp
  • Set
  • String
  • Symbol
  • (…)

There’s also a proposal to include a new BigInt numeric type, which some browsers already support, in ES11. Other than that, any custom data types that you might define are going to be reference types.

Variables of primitive types are stored in a special memory area called the stack, which is fast but has a limited size and is short-lived. Conversely, objects with reference types are allocated on the heap, which is only restricted by the amount of physical memory available on your computer. Such objects have a much longer life cycle but are slightly slower to access.

Primitive types are bare values without any attributes or methods to call. However, as soon as you try to access one using dot notation, the JavaScript engine will instantly wrap a primitive value in the corresponding wrapper object:

> 'Lorem ipsum'.length

Even though a string literal in JavaScript is a primitive data type, you can check its .length attribute. What happens under the hood is that your code is replaced with a call to the String object’s constructor:

> new String('Lorem ipsum').length

A constructor is a special function that creates a new instance of a given type. You can see that the .length attribute is defined by the String object. This wrapping mechanism is known as autoboxing and was copied directly from the Java programming language.

The other and more tangible difference between primitive and reference types is how they’re passed around. Specifically, whenever you assign or pass a value of a primitive type, you actually create a copy of that value in memory. Here’s an example:

> x = 42
> y = x
> x++  // This is short for x += 1
> console.log(x, y)
43 42

The assignment y = x creates a new value in memory. Now you have two distinct copies of the number 42 referenced by x and y, so incrementing one doesn’t affect the other.

However, when you pass a reference to an object literal, then both variables point to the same entity in memory:

> x = {name: 'Person1'}
> y = x
> = 'Person2'
> console.log(y)
{name: 'Person2'}

Object is a reference type in JavaScript. Here, you’ve got two variables, x and y, referring to the same instance of a Person object. The change made to one of the variables is reflected in the other variable.

Last but not least, primitive types are immutable, which means that you can’t change their state once they are initialized. Every modification, such as incrementing a number or making text uppercase, results in a brand-new copy of the original value. While this is a bit wasteful, there are plenty of good reasons to use immutable values, including thread safety, simpler design, and consistent state management.

To check if a variable is a primitive type or a reference type in JavaScript, you can use the built-in typeof operator:

> typeof 'Lorem ipsum'
> typeof new String('Lorem ipsum')

For reference types, the typeof operator always returns a generic "object" string.

If you want to obtain a more detailed information about a particular type, then you have a couple of options:

> today = new Date()
> today instanceof Date
> Date.prototype.isPrototypeOf(today)

You can try checking an object’s constructor name using the instanceof operator, or you can test if it’s derived from a particular parent type with the .prototype property.

Type Hierarchy

Python and JavaScript are object-oriented programming languages. They both allow you to express code in terms of objects that encapsulate identity, state, and behavior. While most programming languages, including Python, use class-based inheritance, JavaScript is one of a few that don’t.

To create hierarchies of custom types in JavaScript, you need to become familiar with prototypal inheritance. That is often one the most challenging concepts to understand when you make a switch from a more classical inheritance model. If you have twenty minutes, then you can watch a great video on prototypes that clearly explains the concept.

The gist of the story is that there are no classes in JavaScript. Well, technically, you can use the class keyword that was introduced in ES6, but it’s purely a syntactic sugar to make things easier for newcomers. Prototypes are still used behind the scenes, so it’s worthwhile to get a closer look at them, which you’ll have a chance to do later on.

Function Type

Lastly, functions are an interesting part of the JavaScript and Python type systems. In both languages, they’re often referred to as first-class citizens or first-class objects because the interpreter doesn’t treat them any differently than other data types. You can pass a function as an argument, return it from another function, or store it in a variable just like a regular value.

This is a very powerful feature that allows you to define higher-order functions and to take full advantage of the functional paradigm. For languages in which functions are special entities, you can work around this with the help of design patterns such as the strategy pattern.

JavaScript is even more flexible than Python in regard to functions. You can define an anonymous function expression full of statements with side effects, whereas Python’s lambda function must contain exactly one expression and no statements:

let countdown = 5;
const id = setInterval(function() {
  if (countdown > 0) {
  } else if (countdown === 0) {
}, 1000);

The built-in setInterval() lets you execute a given function periodically in time intervals expressed in milliseconds until you call clearInterval() with the corresponding ID. Notice the use of a conditional statement and the mutation of a variable from the outer scope of the function expression.

JavaScript Syntax

JavaScript and Python are both high-level scripting languages that share a fair bit of syntactical similarities. This is especially true of their latest versions. That said, JavaScript was designed to resemble Java, whereas Python was modeled after the ABC and Modula-3 languages.

Code Blocks

One of the hallmarks of Python is the use of mandatory indentation to denote a block of code, which is quite unusual and frowned upon by new Python converts. Many popular programming languages, including JavaScript, use curly brackets or special keywords instead:

function fib(n)
  if (n > 1) {
    return fib(n-2) + fib(n-1);
  return 1;

In JavaScript, every block of code consisting of more than one line needs an opening { and a closing }, which gives you the freedom to format your code however you like. You can mix tabs with spaces and don’t need to pay attention to your bracket placement.

Unfortunately, this can result in messy code and sectarian conflicts between developers with different style preferences. This makes code reviews problematic. Therefore, you should always establish coding standards for your team and use them consistently, preferably in an automated way.

Speaking of indentation, it’s customary for JavaScript code to be formatted using two spaces per indentation level instead of the recommended four in Python.


To reduce friction for those making a switch from Java or another C-family programming language, JavaScript terminates statements with a familiar semicolon (;). If you’ve ever programmed in one of those languages, then you’ll know that putting a semicolon after an instruction becomes muscle memory:

alert('hello world');

Semicolons aren’t required in JavaScript, though, because the interpreter will take a guess and insert one for you automatically. In most cases, it’ll be right, but sometimes it may lead to peculiar results.

People have strong opinions on whether to use the semicolon explicitly or not. While there are a few corner cases in which it matters, it’s largely just a convention.


Identifiers, such as variable or function names, must be alphanumeric in JavaScript and Python. In other words, they can only contain letters, digits, and a few special characters. At the same time, they can’t start with a digit. While non-Latin characters are allowed, you should generally avoid them:

  • Legal: foo, foo42, _foo, $foo, fößar
  • Illegal: 42foo

Names in both languages are case sensitive, so variables like foo and Foo are distinct. Nonetheless, the naming conventions in JavaScript are slightly different than in Python:

Python JavaScript
Type ProjectMember ProjectMember
Variable, Attribute, or Function first_name firstName

In general, Python recommends using lower_case_with_underscores, also known as snake_case, for compound names, so that individual words get separated with an underscore character (_). The only exception to that rule is classes, whose names should follow the CapitalizedWords, or Pascal case, style. JavaScript also uses CapitalizedWords for types but mixedCase, or lower camelCase, for everything else.


JavaScript has single-line as well as multiline comments:

x++;  // This is a single-line comment

 This whole paragraph
 is a comment and will
 be ignored.

You can start a comment anywhere on a line using a double slash (//), which is similar to Python’s hash sign (#). While there are no multiline comments in Python, you can simulate them by enclosing a fragment of code within a triple quote (''') to create a multiline string. Alternatively, you can wrap it in an if statement that never evaluates to True:

if False:

You can use this trick, for example, to temporarily disable an existing block of code during debugging.

String Literals

To define string literals in JavaScript, you can use a pair of single quotes (') or double quotes (") interchangeably, just like in Python. However, for a long time, there was no way to define multiline strings in JavaScript. Only ES6 in 2015 brought template literals, which look like a hybrid of f-strings and multiline strings borrowed from Python:

var name = 'John Doe';
var message = `Hi ${name.split(' ')[0]},

We're writing to you regarding...

Kind regards,

A template starts with a backtick (`), also known as the grave accent, instead of regular quotes. To interpolate a variable or any legal expression, you have to use the dollar sign followed by a pair of matching curly brackets: ${...}. This is different from Python’s f-strings, which don’t require the dollar sign.

Variable Scopes

When you define a variable in JavaScript the same way that you would normally do in Python, you’re implicitly creating a global variable. Since global variables break encapsulation, you should rarely need them! The correct way to declare variables in JavaScript has always been through the var keyword:

x = 42;     // This is a global variable. Did you really mean that?
var y = 15; // This is global only when declared in a global context.

Unfortunately, this doesn’t declare a truly local variable, and it has its own problems that you’ll find out about in the upcoming section. Since ES6, there’s been a better way to declare variables and constants with the let and const keywords, respectively:

> let name = 'John Doe';
> const PI = 3.14;
> PI = 3.1415;
TypeError: Assignment to constant variable.

Unlike constants, variables in JavaScript don’t need an initial value. You can provide one later:

let name;
name = 'John Doe';

When you leave off the initial value, you create what’s called a variable declaration rather than a variable definition. Such variables automatically receive a special value of undefined, which is one of the primitive types in JavaScript. This is different in Python, where you always define variables except for variable annotations. But even then, these variables aren’t technically declared:

name: str
name = 'John Doe'

Such an annotation doesn’t affect the variable life cycle. If you referred to name before the assignment, then you’d receive a NameError exception.

Switch Statements

If you’ve been complaining about Python not having a proper switch statement, then you’ll be happy to learn that JavaScript does:

// As with C, clauses will fall through unless you break out of them.
switch (expression) {
  case 'kilo':
    value = bytes / 2**10;
  case 'mega':
    value = bytes / 2**20;
  case 'giga':
    value = bytes / 2**30;
    console.log(`Unknown unit: "${expression}"`);

The expression can evaluate to any type, including a string, which wasn’t always the case in the older Java versions that influenced JavaScript. By the way, did you notice the familiar exponentiation operator (**) in the code snippet above? It wasn’t available in JavaScript until ES7 in 2016.


There’s no native enumeration type in pure JavaScript, but you can use the enum type in TypeScript or emulate one with something similar to this:

const Sauce = Object.freeze({
  BBQ: Symbol('bbq'),
  CHILI: Symbol('chili'),
  GARLIC: Symbol('garlic'),
  KETCHUP: Symbol('ketchup'),
  MUSTARD: Symbol('mustard')

Freezing an object prevents you from adding or removing its attributes. This is different from a constant, which can be mutable! A constant will always point to the same object, but the object itself might change its value:

> const fruits = ['apple', 'banana'];
> fruits.push('orange'); // ['apple', 'banana', 'orange']
> fruits = [];
TypeError: Assignment to constant variable.

You can add an orange to the array, which is mutable, but you can’t modify the constant that is pointing to it.

Arrow Functions

Until ES6, you could only define a function or an anonymous function expression using the function keyword:

function add(a, b) {
  return a + b;

let add = function(a, b) {
  return a + b;

However, to reduce the boilerplate code and to fix a slight problem with binding functions to objects, you can now use the arrow function in addition to the regular syntax:

let add = (a, b) => a + b;

Notice that there’s no function keyword anymore, and the return statement is implicit. The arrow symbol (=>) separates the function’s arguments from its body. People sometimes call it the fat arrow function because it was originally borrowed from CoffeeScript, which also has a thin arrow (->) counterpart.

Arrow functions are most suitable for small, anonymous expressions like lambdas in Python, but they can contain multiple statements with side effects if needed:

let add = (a, b) => {
  const result = a + b;
  return result;

When you want to return an object literal from an arrow function, you need to wrap it in parentheses to avoid ambiguity with a block of code:

let add = (a, b) => ({
  result: a + b

Otherwise, the function body would be confused for a block of code without any return statements, and the colon would create a labeled statement rather than a key-value pair.

Default Arguments

Starting with ES6, function arguments can have default values like in Python:

> function greet(name = 'John') {
   console.log('Hello', name);
> greet();
Hello John

Unlike Python, however, the default values are resolved every time the function is called instead of only when it’s defined. This makes it possible to safely use mutable types as well as to dynamically refer to other arguments passed at runtime:

> function foo(a, b=a+1, c=[]) {
> foo(1);
[1, 2]
> foo(5);
[5, 6]

Every time you call foo(), its default arguments are derived from the actual values passed to the function.

Variadic Functions

When you want to declare a function with variable number of parameters in Python, you take advantage of the special *args syntax. The JavaScript equivalent would be the rest parameter defined with the spread (...) operator:

> function average(...numbers) {
   if (numbers.length > 0) {
     const sum = numbers.reduce((a, x) => a + x);
     return sum / numbers.length;
   return 0;
> average();
> average(1);
> average(1, 2);
> average(1, 2, 3);

The spread operator can also be used to combine iterable sequences. For example, you can extract the elements of one array into another:

const redFruits = ['apple', 'cherry'];
const fruits = ['banana', ...redFruits];

Depending on where you place the spread operator in the target list, you may prepend or append elements or insert them somewhere in the middle.

Destructuring Assignments

To unpack an iterable into individual variables or constants, you can use the destructuring assignment:

> const fruits = ['apple', 'banana', 'orange'];
> const [a, b, c] = fruits;
> console.log(b);

Similarly, you can destructure and even rename object attributes:

const person = {name: 'John Doe', age: 42, married: true};
const {name: fullName, age} = person;
console.log(`${fullName} is ${age} years old.`);

This helps avoid name collisions for variables defined within one scope.

with Statements

There’s an alternative way to drill down to an object’s attributes using the slightly old with statement:

const person = {name: 'John Doe', age: 42, married: true};
with (person) {
  console.log(`${name} is ${age} years old.`);

It works like a construct in Object Pascal, in which a local scope gets temporarily augmented with attributes of the given object.

Since this might be obscure, the with statement is generally discouraged and is even unavailable in strict mode.

Iterables, Iterators, and Generators

Since ES6, JavaScript has had the iterable and iterator protocols as well as generator functions, which look almost identical to Python’s iterables, iterators, and generators. To turn a regular function into a generator function, you need to add an asterisk (*) after the function keyword:

function* makeGenerator() {}

You can’t make generator functions out of arrow functions, though.

When you call a generator function, it won’t execute the body of that function. Instead, it returns a suspended generator object that conforms to the iterator protocol. To advance your generator, you can call .next(), which is similar to Python’s built-in next():

> const generator = makeGenerator();
> const {value, done} =;
> console.log(value);
> console.log(done);

As a result, you’ll always get a status object with two attributes: the subsequent value and a flag that indicates if the generator has been exhausted. Python throws the StopIteration exception when there are no more values in the generator.

To return some value from your generator function, you can use either the yield keyword or the return keyword. The generator will keep feeding values until there are no more yield statements, or until you return prematurely:

let shouldStopImmediately = false;

function* randomNumberGenerator(maxTries=3) {
  let tries = 0;
  while (tries++ < maxTries) {
    if (shouldStopImmediately) {
      return 42; // The value is optional
    yield Math.random();

The above generator will keep yielding random numbers until it reaches the maximum number of tries or you set a flag to make it terminate early.

The equivalent of the yield from expression in Python, which delegates the iteration to another iterator or an iterable object, is the yield* expression:

> function* makeGenerator() {
   yield 1;
   yield* [2, 3, 4];
   yield 5;
> const generator = makeGenerator()
{value: 1, done: false}
{value: 2, done: false}
{value: 3, done: false}
{value: 4, done: false}
{value: 5, done: false}
{value: undefined, done: true}

Interestingly, it’s legal to return and yield at the same time:

function* makeGenerator() {
  return yield 42;

However, due to a grammar limitation, you’d have to use parentheses to achieve the same effect in Python:

def make_generator():
    return (yield 42)

To explain what’s going on, you can rewrite that example by introducing a helper constant:

function* makeGenerator() {
  const message = yield 42;
  return message;

If you know coroutines in Python, then you’ll remember that generator objects can be both producers and consumers. You can send arbitrary values into a suspended generator by providing an optional argument to .next():

> function* makeGenerator() {
   const message = yield 'ping';
   return message;
> const generator = makeGenerator();
{value: "ping", done: false}
{value: "pong", done: true}

The first call to .next() runs the generator until the first yield expression, which happens to return "ping". The second call passes a "pong" that is stored in the constant and immediately returned from the generator.

Asynchronous Functions

The nifty mechanism explored above was the basis for asynchronous programming and the adoption of the async and await keywords in Python. JavaScript followed the same path by bringing in asynchronous functions with ES8 in 2017.

While a generator function returns a special kind of iterator, the generator object, asynchronous functions always return a promise, which was first introduced in ES6. A promise represents the future result of an asynchronous call such as fetch() from the Fetch API.

When you return any value from an asynchronous function, it’s automatically wrapped in a promise object that can be awaited in another asynchronous function:

async function greet(name) {
  return `Hello ${name}`;

async function main() {
  const promise = greet('John');
  const greeting = await promise;
  console.log(greeting); // "Hello John"


Typically, you’d await and assign the result in one go:

const greeting = await greet('John');

Although you can’t completely get rid of promises with asynchronous functions, they significantly improve your code readability. It starts to look like synchronous code even though your functions can be paused and resumed multiple times.

One notable difference from the asynchronous code in Python is that, in JavaScript, you don’t need to manually set up the event loop, which runs in the background implicitly. JavaScript is inherently asynchronous.

Objects and Constructors

You know from an earlier part of this article that JavaScript doesn’t have a concept of classes. Instead, it knows about objects. You can create new objects using object literals, which look like Python dictionaries:

let person = {
  name: 'John Doe',
  age: 42,
  married: true

It behaves like a dictionary in that you can access individual attributes using dot syntax or square brackets:

> person.age++;
> person['age'];

Object attributes don’t need to be enclosed in quotes unless they contain spaces, but that isn’t a common practice:

> let person = {
   'full name': 'John Doe'
> person['full name'];
'John Doe'
> person.full name;
SyntaxError: Unexpected identifier

Just like a dictionary and some objects in Python, objects in JavaScript have dynamic attributes. That means you can add new attributes or delete existing ones from an object:

> let person = {name: 'John Doe'};
> person.age = 42;
> console.log(person);
{name: "John Doe", age: 42}
> delete;
> console.log(person);
{age: 42}

Starting from ES6, objects can have attributes with computed names:

> let person = {
   ['full' + 'Name']: 'John Doe'
> person.fullName;
'John Doe'

Python dictionaries and JavaScript objects are allowed to contain functions as their keys and attributes. There are ways to bind such functions to their owner so that they behave like class methods. For example, you can use a circular reference:

> let person = {
   name: 'John Doe',
   sayHi: function() {
     console.log(`Hi, my name is ${}.`);
> person.sayHi();
Hi, my name is John Doe.

sayHi() is tightly coupled to the object it belongs to because it refers to the person variable by name. If you were to rename that variable at some point, then you’d have to go through the whole object and make sure to update all occurrences of that variable name.

A slightly better approach takes advantage of the implicit this variable that is exposed to functions. The value of this can be different depending on who’s calling the function:

> let jdoe = {
   name: 'John Doe',
   sayHi: function() {
     console.log(`Hi, my name is ${}.`);
> jdoe.sayHi();
Hi, my name is John Doe.

After replacing a hard-coded person with this, which is similar to Python’s self, it won’t matter what the variable name is, and the result will be the same as before.

That’s great, but as soon as you decide to introduce more objects of the same Person kind, you’ll have to repeat all attributes and redefine all functions in each object. What you’d rather have is a template for Person objects.

The canonical way of creating custom data types in JavaScript is to define a constructor, which is an ordinary function:

function Person() {
  console.log('Calling the constructor');

As a convention, to denote that such a function has a special meaning, you’d capitalize the first letter to follow CapitalizedWords instead of the usual mixedCase.

On the syntactical level, however, it’s just a function that you can call normally:

> Person();
Calling the constructor

What makes it special is how you call it:

> new Person();
Calling the constructor
Person {}

When you add the new keyword in front of the function call, it’ll implicitly return a brand-new instance of a JavaScript object. That means your constructor shouldn’t contain the return statement.

While the interpreter is responsible for allocating memory for and scaffolding a new object, the role of a constructor is to give the object an initial state. You can use the previously mentioned this keyword to refer to a new instance under construction:

function Person(name) { = name;
  this.sayHi = function() {
    console.log(`Hi, my name is ${}.`);

Now you can create multiple distinct Person entities:

const jdoe = new Person('John Doe');
const jsmith = new Person('John Smith');

Alright, but you’re still duplicating function definitions across all instances of the Person type. The constructor is just a factory that hooks the same values to individual objects. It’s wasteful and could lead to inconsistent behavior if you were to change it at some point. Consider this:

> const jdoe = new Person('John Doe');
> const jsmith = new Person('John Smith');
> jsmith.sayHi = _ => console.log('What?');
> jdoe.sayHi();
Hi, my name is John Doe.
> jsmith.sayHi();

Since every object gets a copy of its attributes, including functions, you must carefully update all instances to keep a uniform behavior. Otherwise, they’ll do different things, which typically isn’t what you want. Objects might have a different state, but their behavior usually won’t change.


As a rule of thumb, you should move the business logic from the constructor, which is concerned about data, to the prototype object:

function Person(name) { = name;

Person.prototype.sayHi = function() {
  console.log(`Hi, my name is ${}.`);

Every object has a prototype. You can access your custom data type’s prototype by referring to the .prototype attribute of your constructor. It’ll already have a few predefined attributes, such as .toString(), that are common to all objects in JavaScript. You can add more attributes with your custom methods and values.

When JavaScript is looking for an object’s attribute, it begins by trying to find it in that object. Upon failure, it moves on to the respective prototype. Therefore, attributes defined in a prototype are shared across all instances of the corresponding type.

Prototypes are chained, so the attribute lookup continues until there are no more prototypes in the chain. This is analogous to type hierarchy through inheritance.

Not only can you create methods in one place thanks to the prototypes, but you can also create static attributes by attaching them to one:

> Person.prototype.PI = 3.14;
> new Person('John Doe').PI;
> new Person('John Smith').PI;

To illustrate the power of prototypes, you may try to extend the behavior of existing objects, or even a built-in data type. Let’s add a new method to the string type in JavaScript by specifying it in the prototype object:

String.prototype.toSnakeCase = function() {
  return this.replace(/\s+/g, '')
             .map(x => x.toLowerCase())

It uses regular expressions to transform the text into snake_case. Suddenly, string variables, constants, and even string literals can benefit from it:

> "loremIpsumDolorSit".toSnakeCase();

However, this is a double-edged sword. In a similar way, someone could override one of the existing methods in a prototype of a popular type, which would break the assumptions made elsewhere. Such monkey patching can be useful in testing but is otherwise very dangerous.


Since ES6, there’s been an alternative way to define prototypes that uses a much more familiar syntax:

class Person {
  constructor(name) { = name;
  sayHi() {
    console.log(`Hi, my name is ${}.`);

Even though this looks like you’re defining a class, it’s only a convenient high-level metaphor for specifying custom data types in JavaScript. Behind the scenes, there are no real classes! For that reason, some people go so far as to advocate against using this new syntax at all.

You can have getters and setters in your class, which are similar to the Python’s class properties:

> class Square {
   constructor(size) {
     this.size = size; // Triggers the setter
   set size(value) {
     this._size = value; // Sets the private field
   get area() {
     return this._size**2;
> const box = new Square(3);
> console.log(box.area);
> box.size = 5;
> console.log(box.area);

When you omit the setter, you create a read-only property. That’s misleading, however, because you can still access the underlying private field like you can in Python.

A common pattern to encapsulate internal implementation in JavaScript is an Immediately Invoked Function Expression (IIFE), which can look like this:

> const odometer = (function(initial) {
   let mileage = initial;
   return {
     get: function() { return mileage; },
     put: function(miles) { mileage += miles; }
> odometer.put(65);
> odometer.put(12);
> odometer.get();

In other words, it’s an anonymous function that calls itself. You can use the newer arrow function to create an IIFE too:

const odometer = ((initial) => {
  let mileage = initial;
  return {
    get: _ => mileage,
    put: (miles) => mileage += miles

This is how JavaScript historically emulated modules to avoid name collisions in the global namespace. Without an IIFE, which uses closures and function scope to expose only a limited public-facing API, everything would be accessible from the calling code.

Sometimes you want to define a factory or a utility function that logically belongs to your class. In Python, you have the @classmethod and @staticmethod decorators, which allow you to associate static methods with the class. To achieve the same result in JavaScript, you need to use the static method modifier:

class Color {
  static brown() {
    return new Color(244, 164, 96);
  static mix(color1, color2) {
    return new Color(
      (x, i) => (x + color2.channels[i]) / 2
  constructor(r, g, b) {
    this.channels = [r, g, b];
const color1 = Color.brown();
const color2 = new Color(128, 0, 128);
const blended = Color.mix(color1, color2);

Note that there’s no way of defining static class attributes at the moment, at least not without additional transpiler plugins.

Chaining prototypes can resemble class inheritance when you extend one class from another:

class Person {
  constructor(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  fullName() {
    return `${this.firstName} ${this.lastName}`;

class Gentleman extends Person {
  signature() {
    return 'Mr. ' + super.fullName()

In Python, you could extend more than one class, but that it isn’t possible in JavaScript. To reference attributes from the parent class, you can use super(), which has to be called in the constructor to pass arguments up the chain.


Decorators are yet another feature that JavaScript copied from Python. They’re still technically a proposal that is subject to change, but you can test them out using an online playground or a local transpiler. Be warned, however, that they require a bit of configuration. Depending on the chosen plugin and its options, you’ll get different syntax and behavior.

Several frameworks already use a custom syntax for decorators, which need to be transpiled into plain JavaScript. If you opt for the TC-39 proposal, then you’ll be able to decorate only classes and their members. It seems there won’t be any special syntax for function decorators in JavaScript.

JavaScript Quirks

It took ten days for Brendan Eich to create a prototype of what later became JavaScript. After it was presented to the stakeholders at a business meeting, the language was considered production ready and didn’t go through a lot of changes for many years.

Unfortunately, that made the language infamous for its oddities. Some people didn’t even regard JavaScript as a “real” programming language, which made it a victim of many jokes and memes.

Today, the language is much friendlier than it used to be. Nevertheless, it’s worth knowing what to avoid, since a lot of legacy JavaScript is still out there waiting to bite you.

Bogus Array

Python’s lists and tuples are implemented as arrays in the traditional sense, whereas JavaScript’s Array type has more in common with Python’s dictionary. What’s an array, then?

In computer science, an array is a data structure that occupies a contiguous block of memory, and whose elements are ordered and have homogeneous sizes. This way, you can access them randomly with a numerical index.

In Python, a list is an array of pointers that are typically integer numbers, which reference heterogeneous objects scattered around in various regions of memory.

JavaScript’s array is an object whose attributes happen to be numbers. They’re not necessarily stored next to each other. However, they keep the right order during iteration.

When you delete an element from an array in JavaScript, you make a gap:

> const fruits = ['apple', 'banana', 'orange'];
> delete fruits[1];
> console.log(fruits);
['apple', empty, 'orange']
> fruits[1];

The array doesn’t change its size after the removal of one of its elements:

> console.log(fruits.length);

Conversely, you can put a new element at a distant index even though the array is much shorter:

> fruits[10] = 'watermelon';
> console.log(fruits.length);
> console.log(fruits);
['apple', empty, 'orange', empty × 7, 'watermelon']

This wouldn’t work in Python.

Array Sorting

Python is clever about sorting data because it can tell the difference between element types. When you sort a list of numbers, for example, it’ll put them in ascending order by default:

>>> sorted([53, 2020, 42, 1918, 7])
[7, 42, 53, 1918, 2020]

However, if you wanted to sort a list of strings, then it would magically know how to compare the elements so that they appear in lexicographical order:

>>> sorted(['lorem', 'ipsum', 'dolor', 'sit', 'amet'])
['amet', 'dolor', 'ipsum', 'lorem', 'sit']

Things get complicated when you start to mix different types:

>>> sorted([42, 'not a number'])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: '<' not supported between instances of 'str' and 'int'

By now, you know that Python is a strongly typed language and doesn’t like mixing types. JavaScript, on the other hand, is the opposite. It’ll eagerly convert elements of incompatible types according to some obscure rules.

You can use .sort() to do the sorting in JavaScript:

> ['lorem', 'ipsum', 'dolor', 'sit', 'amet'].sort();
['amet', 'dolor', 'ipsum', 'lorem', 'sit']

It turns out that sorting strings works as expected. Let’s see how it copes with numbers:

> [53, 2020, 42, 1918, 7].sort();
[1918, 2020, 42, 53, 7]

What happened here is that the array elements got implicitly converted to strings and were sorted lexicographically. To prevent that, you have to provide your custom sorting strategy as a function of two elements to compare, for example:

> [53, 2020, 42, 1918, 7].sort((a, b) => a - b);
[7, 42, 53, 1918, 2020]

The contract between your strategy and the sorting method is that your function should return one of three values:

  1. Zero when the two elements are equal
  2. A positive number when elements need to be swapped
  3. A negative number when the elements are in the right order

This is a common pattern present in other languages, and it was also the old way of sorting in Python.

Automatic Semicolon Insertion

At this point, you know that semicolons in JavaScript are optional because the interpreter will insert them automatically at the end of each instruction if you don’t do so yourself.

This can lead to surprising results under some circumstances:

function makePerson(name) {
      fullName: name,
      createdAt: new Date()

In this example, you might expect the JavaScript engine to insert a missing semicolon at the very end of your function, right after the closing parenthesis of the object literal. However, when you call the function, this happens:

> const jdoe = makePerson('John Doe');
> console.log(jdoe);

Your function changed the intended action by returning an undefined because two semicolons were inserted instead of one:

function makePerson(name) {
      fullName: name,
      createdAt: new Date()

As you can see, relying on the fact that semicolons are optional introduces some risk of errors in your code. On the other hand, it won’t help if you start putting semicolons everywhere.

To fix this example, you need to change your code formatting so that the returned value begins on the same line as the return statement:

function makePerson(name) {
  return {
    fullName: name,
    createdAt: new Date()

In some situations, you can’t rely on automatic semicolon insertion and you need to put one explicitly instead. For example, you can’t leave out the semicolon when you start a new line with a parenthesis:

const total = 2 + 3
(4 + 5).toString()

This produces a runtime error due to the lack of a semicolon, which makes the two lines collapse into one:

const total = 2 + 3(4 + 5).toString();

A numeric literal can’t be called like a function.

Confusing Loops

Loops in JavaScript are particularly confusing because there are so many of them and they look alike, whereas Python has just two. The primary type of loop in JavaScript is the for loop, which was transplanted from Java:

const fruits = ['apple', 'banana', 'orange'];
for (let i = 0; i < fruits.length; i++) {

It has three parts, all of which are optional:

  1. Initialization: let i = 0
  2. Condition: i < fruits.length
  3. Cleanup: i++

The first part executes only once before the loop starts, and it typically sets the initial value for the counter. Then, after each iteration, the cleanup part runs to update the counter. Right after that, the condition is evaluated to determine if the loop should continue. This is roughly equivalent to iterating over a list of indices in Python:

fruits = ['apple', 'banana', 'orange']
for i in range(len(fruits)):

Notice how much work Python does for you. On the other hand, having the loop internals exposed gives you a lot of flexibility. This type of loop is generally deterministic because you know how many times it’ll iterate from the beginning.

In JavaScript, you can make the conventional for loop non-deterministic and even infinite by omitting one or more of its parts:

for (;;) {
  // An infinite loop

However, a more idiomatic way to make such an iteration would involve the while loop, which is quite similar to the one you’d find in Python:

while (true) {
  const age = prompt('How old are you?');
  if (age >= 18) {

In addition to this, JavaScript has a do...while loop, which is guaranteed to run at least once because it checks the condition after its body. You can rewrite this example in the following way:

let age;
do {
  age = prompt('How old are you?');
} while (age < 18);

Apart from stopping an iteration midway with the break keyword, you can skip to the next iteration using the continue keyword as you would in Python:

for (let i = 0; i < 10; i++) {
  if (i % 2 === 0) {

What you can’t do, though, is use the else clause on loops.

You might be tempted to try out the loop in JavaScript, thinking it would iterate over values like a Python for loop. Although it looks similar and has a similar name, it actually behaves very differently!

A loop in JavaScript iterates over attributes of the given object, including the ones found in the prototype chain:

> const object = {name: 'John Doe', age: 42};
> for (const attribute in object) {
   console.log(`${attribute} = ${object[attribute]}`);
name = John Doe
age = 42

Should you want to exclude attributes attached to the prototype, you can call hasOwnProperty(). It will test whether a given attribute belongs to an object instance.

When you feed the loop with an array, it’ll iterate over the array’s numeric indices. As you know by now, arrays in JavaScript are just glorified dictionaries:

> const fruits = ['apple', 'banana', 'orange'];
 for (const fruit in fruits) {

On the other hand, arrays expose .forEach(), which can substitute for a loop:

const fruits = ['apple', 'banana', 'orange'];
fruits.forEach(fruit => console.log(fruit));

This is a higher-order function that accepts a callback that will run for every element in the array. This pattern fits a bigger picture since JavaScript takes a functional approach to iteration in general.

Finally, when the ES6 specification introduced the iterable and iterator protocols, it allowed the implementation of a long-awaited loop that would iterate over sequences. However, since the name was already taken, they had to come up with a different one.

The for...of loop is the closest relative to the for loop in Python. With it, you can iterate over any iterable object, including strings and arrays:

const fruits = ['apple', 'banana', 'orange'];
for (const fruit of fruits) {

This is probably the most intuitive way for a Python programmer to iterate in JavaScript.

Constructor Without new

Let’s go back to the Person type defined earlier:

function Person(name) { = name;
  this.sayHi = function() {
    console.log(`Hi, my name is ${}.`);

If you forget to call that constructor correctly, with the new keyword in front of it, then it’ll fail silently and leave you with an undefined variable:

> let bob = Person('Bob');
> console.log(bob);

There’s a trick to protect yourself against this mistake. When you omit the new keyword, there won’t be any object to bind to, so the this variable inside the constructor will point to the global object, such as the window object in the web browser. You can detect that and delegate to a valid constructor invocation:

> function Person(name) {
   if (this === window) {
     return new Person(name);
   } = name;
   this.sayHi = function() {
     console.log(`Hi, my name is ${}.`);
> let person = Person('John Doe');
> console.log(person);
Person {name: 'John Doe', sayHi: ƒ}

This is the only reason you might want to include a return statement in your constructor.

Global Scope by Default

Unless you’re already at the global scope, your variables automatically become global when you don’t precede their declarations with one of these keywords:

  • var
  • let
  • const

It’s easy to fall into this trap, especially when you’re coming from Python. For example, such a variable defined in a function will become visible outside of it:

> function call() {
   global = 42;
   let local = 3.14
> call();
> console.log(global);
> console.log(local);
ReferenceError: local is not defined

Interestingly, the rules determining whether you declare a local or a global variable in Python are much more complicated than this. There are also other kinds of variable scope in Python.

Function Scope

This quirk is only present in legacy code, which uses the var keyword for variable declaration. You’ve learned that when a variable is declared like that, it won’t be global. But it isn’t going to have a local scope either.

No matter how deep in the function a variable is defined, it’ll be scoped to the entire function:

> function call() {
   if (true) {
     for (let i = 0; i < 10; i++) {
       var notGlobalNorLocal = 42 + i;
> call();

The variable is visible and still alive at the top level of the function right before exiting. However, nested functions don’t expose their variables to the outer scope:

> function call() {
   function inner() {
     var notGlobalNorLocal = 42;
> call();
ReferenceError: notGlobalNorLocal is not defined

It works the other way around, though. Inner functions can see the variables from the outer scope, but it gets even more interesting when you return the inner function for later use. This creates a closure.


This one is related to the previous quirk and, again, applies to variables declared with the infamous var keyword.

Let’s begin with a little riddle:

var x = 42;

function call() {
  console.log(x); // A = ???
  var x = 24;
  console.log(x); // B = ???

console.log(x); // C = ???

Consider what the result will be:

  1. A = 42, B = 24, C = 42
  2. A = 42, B = 24, C = 24
  3. A = 24, B = 24, C = 42
  4. A = 24, B = 24, C = 24
  5. SyntaxError: Identifier 'x' has already been declared

While you’re thinking about your answer, let’s take a closer look at what hoisting is. In short, it’s an implicit mechanism in JavaScript that moves variable declarations to the top of the function, but only those that use the var keyword. Keep in mind that it moves declarations rather than definitions.

In some programming languages, like C, all variables must be declared at the beginning of a function. Other languages, such as Pascal, go even further by dedicating a special section for variable declarations. JavaScript tried to mimic that.

Alright, ready? The correct answer is none of the above! It’ll print the following:

  • A = undefined
  • B = 24
  • C = 42

To clarify the reasoning behind these results, you can manually perform the hoisting that JavaScript would do in this situation:

var x = 42;

function call() {
  var x;
  console.log(x); // A = undefined
  x = 24;
  console.log(x); // B = 24

console.log(x); // C = 42

The global variable is temporarily masked by the local variable because the name lookup goes outward. The declaration of x inside the function is moved up. When a variable is declared but not initialized, it has the undefined value.

Variables aren’t the only construct that is affected by hoisting. Normally, when you define a function in JavaScript, you can call it even before its definition:

call(); // Prints "hello"

function call() {

This won’t work in an interactive shell where individual pieces of code are evaluated immediately.

Now, when you declare a variable using the var keyword and assign a function expression to that variable, it’ll be hoisted:

call(); // TypeError: call is not a function

var call = function() {

As a result, your variable will remain undefined until you initialize it.

Illusory Function Signatures

Function signatures don’t exist in JavaScript. Whichever formal parameters you declare, they have no impact on function invocation.

Specifically, you can pass any number of arguments to a function that doesn’t expect anything, and they’ll just be ignored:

> function currentYear() {
   return new Date().getFullYear();
> currentYear(42, 'foobar');

You can also refrain from passing arguments that are seemingly required:

> function truthy(expression) {
   return !!expression;
> truthy();

Formal parameters serve as a documentation and allow you to refer to arguments by name. Otherwise, they’re not needed. Within any function, you have access to a special arguments variable, which represents the actual parameters that were passed:

> function sum() {
   return [...arguments].reduce((a, x) => a + x);
> sum(1, 2, 3, 4);

arguments is an array-like object that is iterable and has numeric indices, but unfortunately it doesn’t come with .forEach(). To wrap it in an array, you can use the spread operator.

This used to be the only way of defining variadic functions in JavaScript before the rest parameter in ES6.

Implicit Type Coercion

JavaScript is a weakly typed programming language, which is manifested in its ability to cast incompatible types implicitly.

This can give false positives when you compare two values:

if ('2' == 2) { // Evaluates to true

In general, you should prefer the strict comparison operator (===) to be safe:

> '2' === 2;
> '2' !== 2;

This operator compares both the values and the types of their operands.

No Integer Type

Python has a few data types to represent numbers:

  • int
  • float
  • complex

The previous Python generation also had the long type, which was eventually merged into int.

Other programming languages are even more generous, giving you fine-grained control over memory consumption, value range, floating-point precision, and the treatment of sign.

JavaScript has just one numeric type: the Number, which corresponds to Python’s float data type. Under the hood, it’s a 64-bit double-precision number that conforms to the IEEE 754 specification. This was simple and sufficient for early web development, but it can cause a few problems today.

First of all, it’s remarkably wasteful in most situations. If you were to represent pixels of a single FHD video frame with JavaScript’s Number, then you’d have to allocate about 50 MB of memory. In a programming language with support for a stream of bytes, such as Python, you’d need a fraction of that amount of memory.

Secondly, floating-point numbers suffer from a rounding error due to how they’re represented in computer memory. As such, they’re unsuitable for applications requiring high precision, such as monetary calculations:

> 0.1 + 0.2;

They’re unsafe in representing very big and very small numbers:

> const x = Number.MAX_SAFE_INTEGER + 1;
> const y = Number.MAX_SAFE_INTEGER + 2;
> x === y;

But that’s not the worst part. After all, computer memory is getting cheaper by the day, and there are ways to circumvent the rounding error.

When Node.js became popular, people started using it to write back-end applications. They needed a way of accessing the local file system. Some operating systems identify files by arbitrary integer numbers. Occasionally, these numbers wouldn’t have an exact representation in JavaScript, so you couldn’t open the file, or you’d read some random file without knowing.

To address the problem of handling big numbers in JavaScript, there’s going to be another primitive type that can reliably represent integer numbers of any size. Some web browsers already support this proposal:

> const x = BigInt(Number.MAX_SAFE_INTEGER) + 1n;
> const y = BigInt(Number.MAX_SAFE_INTEGER) + 2n;
> x === y;

Since you can’t mix the new BigInt data type with regular numbers, you have to either wrap them or use a special literal:

> typeof 42;
> typeof 42n;
> typeof BigInt(42);

Apart from that, the BigInt number will be compatible with two somewhat-related typed arrays for signed and unsigned integers:

  1. BigInt64Array
  2. BigUint64Array

While a regular BigInt can store arbitrarily large numbers, the elements of these two arrays are limited to just 64 bits.

null vs undefined

Programming languages provide ways to represent the absence of a value. Python has None, Java has null, and Pascal has nil, for example. In JavaScript, you get not only null but also undefined.

It may seem odd to have more than one way to represent missing values when one is already too much:

I call it my billion-dollar mistake. It was the invention of the null reference in 1965. (…) This has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years.

Tony Hoare

The difference between null and undefined is quite subtle. Variables that are declared but uninitialized will implicitly get the value of undefined. The null value, on the other hand, is never assigned automatically:

let x; // undefined
let y = null;

At any time, you can manually assign the undefined value to a variable:

let z = undefined;

This distinction between null and undefined was often used to implement default function arguments before ES6. One of the possible implementations was this:

function fn(required, optional) {
  if (typeof optional === 'undefined') {
    optional = 'default';
  // ...

If—for whatever reason—you wanted to keep an empty value for the optional parameter, then you couldn’t pass undefined explicitly because it would get overwritten by the default value again.

To differentiate between these two scenarios, you would pass a null value instead:

fn(42);            // optional = "default"
fn(42, undefined); // optional = "default"
fn(42, null);      // optional = null

Apart from having to deal with null and undefined, you may sometimes experience a ReferenceError exception:

> foobar;
ReferenceError: foobar is not defined

This indicates that you’re trying to refer to a variable that hasn’t been declared in the current scope, whereas undefined means declared but uninitialized, and null means declared and initialized but with an empty value.

Scope of this

Methods in Python must declare a special self parameter unless they’re static or class methods. The parameter holds a reference to a particular instance of the class. Its name can be anything because it’s always passed as the first positional argument.

In JavaScript, like in Java, you can take advantage of a special this keyword, which corresponds to the current instance. But what does current instance mean? It depends on how you invoke your function.

Recall the syntax for object literals:

> let jdoe = {
   name: 'John Doe',
   whoami: function() {
> jdoe.whoami();
{name: "John Doe", whoami: ƒ}

Using this in a function lets you refer to a particular object that owns that function without hard-coding a variable name. It doesn’t matter if the function is defined in place as an anonymous expression or if it’s a regular function like this one:

> function whoami() {
> let jdoe = {name: 'John Doe', whoami};
> jdoe.whoami();
{name: "John Doe", whoami: ƒ}

What matters is the object that you’re calling the function on:

> jdoe.whoami();
{name: "John Doe", whoami: ƒ}
> whoami();
Window {}

In the first line, you call whoami() through an attribute of the jdoe object. The value of this is the same as the jdoe variable in that case. However, when you call that same function directly, this becomes the global object instead.

You can think of JavaScript functions as methods attached to the global object. In a web browser, window is the global object, so in reality, the code snippet above is short for this:

> jdoe.whoami();
{name: "John Doe", whoami: ƒ}
> window.whoami();
Window {}

Do you see a pattern here?

By default, the value of this inside a function depends on the object sitting in front of the dot operator. As long as you control how your function gets called, everything will be fine. It becomes a problem only when you don’t call the function yourself, which is a common case for callbacks.

Let’s define another object literal to demonstrate this:

const collection = {
  items: ['apple', 'banana', 'orange'],
  type: 'fruit',
  show: function() {
    this.items.forEach(function(item) {
      console.log(`${item} is a ${this.type}`);

collection is a collection of elements that have a common type. Currently, .show() doesn’t work as expected because it doesn’t reveal an element type:

apple is a undefined
banana is a undefined
orange is a undefined

Although this.items correctly refers to the array of fruits, the callback function seems to have received a different this reference. That’s because the callback isn’t tied to the object literal. It’s as if it were defined elsewhere:

function callback(item) {
  console.log(`${item} is a ${this.type}`);

const collection = {
  items: ['apple', 'banana', 'orange'],
  type: 'fruit',
  show: function() {

The most straightforward way to work around this would be to replace this in the callback with a custom variable or a constant:

const collection = {
  items: ['apple', 'banana', 'orange'],
  type: 'fruit',
  show: function() {
    const that = this;
    this.items.forEach(function(item) {
      console.log(`${item} is a ${that.type}`);

You persist the value of this in a local constant, which the callback then refers to. Since the callback is defined as an inner function, it can access variables and constants from the outer scope. This wouldn’t have been possible with a stand-alone function.

Now the result is correct:

apple is a fruit
banana is a fruit
orange is a fruit

This pattern is so common that .forEach() accepts an optional parameter for substituting this in the callback:

const collection = {
  items: ['apple', 'banana', 'orange'],
  type: 'fruit',
  show: function() {
    this.items.forEach(function(item) {
      console.log(`${item} is a ${this.type}`);
    }, this);

This is more elegant than the custom hack that you saw before, and it’s also more universal because it lets you pass a regular function. While not every built-in method in JavaScript is so gracious, there are three more ways to tinker with the this reference:

  1. .apply()
  2. .bind()
  3. .call()

These are methods available on function objects. With .apply() and .call(), you can invoke a function while injecting arbitrary context to it. They both work the same way but pass arguments using different syntaxes:

> function whoami(x, y) {
   console.log(this, x, y);
> let jdoe = {name: 'John Doe'};
> whoami.apply(jdoe, [1, 2]);
{name: "John Doe"} 1 2
>, 1, 2);
{name: "John Doe"} 1 2

Of the three, .bind() is the most powerful because it allows you to permanently change the value of this for future invocations. It works a bit differently as it returns a new function that is bound to the given context:

> const newFunction = whoami.bind(jdoe);
> newFunction(1, 2);
{name: "John Doe"} 1 2
> newFunction(3, 4);
{name: "John Doe"} 3 4

This can be useful in solving the earlier problem of the unbound callback:

const collection = {
  items: ['apple', 'banana', 'orange'],
  type: 'fruit',
  show: function() {

Nothing that you’ve just read about this in JavaScript applies to arrow functions in ES6. That’s good, by the way, because it removes a lot of ambiguity. There’s no context binding in arrow functions, nor is there an implicit this reference available. Instead, this is treated as an ordinary variable and is subject to lexical scoping rules.

Let’s rewrite one of the previous examples using arrow functions:

> const collection = {
   items: ['apple', 'banana', 'orange'],
   type: 'fruit',
   show: () => {
     this.items.forEach((item) => {
       console.log(`${item} is a ${this.type}`);
TypeError: Cannot read property 'forEach' of undefined

If you trade both functions for their arrow counterparts, then you’ll get an exception because there’s no this variable in the scope anymore. You might keep the outer function so that its this reference can be picked up by the callback:

const collection = {
  items: ['apple', 'banana', 'orange'],
  type: 'fruit',
  show: function() {
    this.items.forEach((item) => {
      console.log(`${item} is a ${this.type}`);

As you can see, arrow functions aren’t a complete replacement for traditional functions.

This section was merely the tip of the iceberg. To find out more about quirky JavaScript behaviors, take a look at tricky code examples, which are also available as an installable Node.js module. Another great source of wisdom is Douglas Crockford’s book JavaScript: The Good Parts, which has a section devoted to the bad and awful parts as well.

What’s Next?

As a Pythonista, you know that mastering a programming language and its ecosystem is only the beginning of your path to success. There are more abstract concepts to grasp along the way.

Document Object Model (DOM)

If you’re planning to do any sort of client-side development, then you can’t escape getting familiar with the DOM.

To allow for manipulating HTML documents in JavaScript, web browsers expose a standard interface called the DOM, which comprises various objects and methods. When a page loads, your script can gain access to the internal representation of the document through a predefined document instance:

const body = document.body;

It’s a global variable available to you anywhere in your code.

Every document is a tree of elements. To traverse this hierarchy, you can start at the root and use the following attributes to move in different directions:

  • Up: .parentElement
  • Left: .previousElementSibling
  • Right: .nextElementSibling
  • Down: .children, .firstElementChild, .lastElementChild

These attributes are conveniently available on all elements in the DOM tree, which would be perfect for recursive traversal:

const html = document.firstElementChild;
const body = html.lastElementChild;
const element = body.children[2].nextElementSibling;

Most attributes will be null if they don’t lead to an element in the tree. The only exception is the .children property, which always returns an array-like object that can be empty.

Frequently, you won’t know where an element is. The document object, as well as every other element in the tree, has a few methods for element lookup. You can search elements by tag name, ID attribute, CSS class name, or even using a complex CSS selector.

You can look for one element at a time or multiple elements at once. For example, to match elements against a CSS selector, you’d call one of these two methods:

  1. .querySelector(selector)
  2. .querySelectorAll(selector)

The first one returns the first occurrence of the matching element or null, while the second method always returns an array-like object with all the matching elements. Calling these methods on the document object will cause the entire document to be searched. You can restrict the scope of the search by calling the same methods on a previously found element:

const div = document.querySelector('div'); // The 1st div in the whole document
div.querySelectorAll('p'); // All paragraphs inside that div

Once you have a reference to an HTML element, you can do a bunch of things with it, such as:

  • Attach data to it
  • Change its style
  • Change its content
  • Change its placement
  • Make it interactive
  • Remove it altogether

You can also create new elements and add them to the DOM tree:

const parent = document.querySelector('.content');
const child = document.createElement('div');

The most challenging part about using DOM is getting skilled at building accurate CSS selectors. You can practice and learn using one of many interactive playgrounds available online.

JavaScript Frameworks

The DOM interface is a set of primitive building blocks for creating interactive user interfaces. It gets the job done, but as your client-side code grows, it becomes increasingly difficult to maintain. The business logic and the presentation layer start to intersect, violating the separation of concerns principle, while code duplication piles up.

Web browsers add fuel to the fire by not having a unified interface across the board. Sometimes, a feature that you seek to use isn’t available on all major browsers or is implemented in different ways. To ensure consistent behavior, you need to include an appropriate boilerplate code that hides the implementation details.

To deal with these problems, people started sharing JavaScript libraries that encapsulated common patterns and made the browser API a little less jarring. The most popular library of all time by far is jQuery, which until recently dwarfed today’s front-end frameworks in popularity:

Search Interest in JavaScript Frameworks According to Google Trends
Search Interest in JavaScript Frameworks According to Google Trends

Although this isn’t a completely honest apples-and-oranges comparison, it shows just how popular jQuery used to be. In peak time, it had more hits than all the other major frameworks and libraries combined. Despite being slightly old and unfashionable, it’s still used in a lot of legacy projects and sometimes even in recent ones.

jQuery has good documentation and a minimal API as there’s only one function to remember, the versatile $(). You can use that function to create and search for elements, change their style, handle events, and much more.

Modern web browsers are much better in terms of consistency and support for emerging web standards. So much so, in fact, that some people choose to develop client-side code in vanilla JavaScript without the help of any front-end framework.

So why do most people tend to use a front-end framework, anyway?

There are pros and cons to using frameworks, but the biggest advantage seems to be that they allow you to operate on a higher level of abstraction. Instead of thinking in terms of document elements, you can build self-contained and reusable components, which increases your productivity as a programmer.

That, combined with declarative state management provided by the framework, allows you to tackle the complexity of a highly interactive client-side JavaScript application. If you insist on not using a JavaScript framework for long enough, then you typically end up unwittingly building your own.

As for which popular framework to choose, that depends on your goal. To stay relevant, you should invest time into learning pure JavaScript first since front-end frameworks come and go notoriously fast.

If you’re looking to land a job as a front-end developer or even as a full-stack engineer, then you should take a look at the job descriptions. Chances are they’ll expect someone with experience in one of these frameworks:

At the time of writing this article, these were arguably the most popular JavaScript frameworks.


In this tutorial, you learned about JavaScript’s origins, its alternatives, and where the language is headed. You compared Python vs JavaScript by taking a closer look at their similarities and differences in syntax, runtime environments, associated tools, and jargon. Finally, you learned how to avoid fatal mistakes in JavaScript.

You’re now able to:

  • Compare Python vs JavaScript
  • Choose the right language for the job
  • Write a shell script in JavaScript
  • Generate dynamic content on a web page
  • Take advantage of the JavaScript ecosystem
  • Avoid common pitfalls in JavaScript

Try using Node.js for your next scripting project or building an interactive client-side application in the web browser. When you combine what you’ve just learned with your existing Python knowledge, the sky’s the limit.

Next Steps

In this tutorial, you’ve learned how JavaScript and Python differ, and you’ve dipped a toe into programming with JavaScript. If you’d like to continue your JavaScript journey, perhaps you’d like to learn a bit more. Below, you can check out some related questions, as well as some resources for further study:

It boils down to personal preference and experience. Most programming languages share the same fundamental concepts, so once you know one language like Python, then learning another one will become easier. Ultimately, you can break down any computer program to a sequence of instructions, conditionals, and loops. Different languages may use alternative syntax, but they express the same underlying idea or algorithm.

If you’re deciding on your first programming language to learn, then it doesn’t really matter which one you choose. At this level of your education, it’s more important to just pick one and focus on learning the universal fundamentals of programming. However, if you know that you’re drawn more toward front-end programming, then learning JavaScript first might set you on the right path.

JavaScript is the language native to web browsers, so you’ll need to learn it at some point if you’re serious about web programming. It lets you interact with the browser and create dynamic content for your web applications. JavaScript is also a popular choice for building mobile or desktop apps.

Becoming acquinated with JavaScript is one of many stepping stones toward becoming a successful web developer. In addition to learning JavaScript, you should at least know about HTML, CSS, the HTTP protocol, browser policies and their differences, JavaScript frameworks, and the entire ecosystem of tools around it.

There are many excellent resources for learning JavaScript, including paid and free options, which you can find online. Mozilla Developer Network (MDN) Web Docs is one of the most comprehensive and up-to-date knowledgebases for JavaScript, featuring tutorials and reference materials for people at different skill levels.

You’ll also find free e-books, such as Eloquent JavaScript by Marijn Haverbeke, Speaking JavaScript by Dr. Axel Rauschmayer, or the You Don’t Know JS Yet book series by Kyle Simpson.

To put your newfound knowledge to the test, start by rewriting one of your existing Python scripts into JavaScript, as this will help you focus on the language differences without thinking about implementation details.

If you have a web project with a Python back end, such as a REST API implemented in Flask or Django, then you may also build a JavaScript front end for it. Remember, practice is the key to mastering any programming language.

Good luck and happy coding!

Watch Now This tutorial has a related video course created by the Real Python team. Watch it together with the written tutorial to deepen your understanding: Python vs JavaScript for Python Developers

🐍 Python Tricks 💌

Get a short & sweet Python Trick delivered to your inbox every couple of days. No spam ever. Unsubscribe any time. Curated by the Real Python team.

Python Tricks Dictionary Merge

About Bartosz Zaczyński

Bartosz Zaczyński Bartosz Zaczyński

Bartosz is a bootcamp instructor, author, and polyglot programmer in love with Python. He helps his students get into software engineering by sharing over a decade of commercial experience in the IT industry.

» More about Bartosz

Each tutorial at Real Python is created by a team of developers so that it meets our high quality standards. The team members who worked on this tutorial are:

Master Real-World Python Skills With Unlimited Access to Real Python

Join us and get access to thousands of tutorials, hands-on video courses, and a community of expert Pythonistas:

Level Up Your Python Skills »

Master Real-World Python Skills
With Unlimited Access to Real Python

Join us and get access to thousands of tutorials, hands-on video courses, and a community of expert Pythonistas:

Level Up Your Python Skills »

What Do You Think?

Rate this article:
Tweet Share Share Email

What’s your #1 takeaway or favorite thing you learned? How are you going to put your newfound skills to use? Leave a comment below and let us know.

Commenting Tips: The most useful comments are those written with the goal of learning from or helping out other students. Get tips for asking good questions and get answers to common questions in our support portal.

Looking for a real-time conversation? Visit the Real Python Community Chat or join the next “Office Hours” Live Q&A Session. Happy Pythoning!

Keep Learning

Related Tutorial Categories: front-end intermediate

Recommended Video Course: Python vs JavaScript for Python Developers