40 ReactJS Interview Questions

Are you prepared for questions like 'How does the `useMemo` hook improve performance in React apps?' and similar? We've collected 40 interview questions for you to prepare for your next ReactJS interview.

How does the `useMemo` hook improve performance in React apps?

The useMemo hook helps improve performance by memoizing the result of a function. It takes a "create" function and a dependency array as arguments. When the dependencies change, the create function runs again, but if they remain the same, React returns the memoized result. This is especially useful for expensive computations or calculations that you don't want to re-run on every render. It ensures that the component only recalculates values when necessary, thus optimizing rendering performance.

Can you explain what React Fiber is?

React Fiber is a complete rewrite of React's core algorithm for rendering and updating the DOM. It's designed to make the reconciliation process more efficient and flexible. Prior to Fiber, React used a stack-based reimplementation; Fiber introduces a new structure that breaks the rendering work into units of work, allowing for async rendering and better handling of complex UI updates. This essentially helps in improving the user experience by enabling smoother animations, better handling of large component trees, and giving higher priority to critical updates.

Describe the React Profiler API and its use.

The React Profiler API is a developer tool designed to help you measure the performance of your React app by collecting timing information about each component's render and commit phases. It allows you to analyze which parts of your application are slow and understand why. By wrapping components with the <Profiler> component and providing an onRender callback, you can gather detailed insights about each render, including the duration, which component caused it, and more. This information is valuable for optimizing rendering performance, particularly in complex or large-scale applications.

What is the difference between `useReducer` and `useState`?

useState is great for managing simple state transformations, especially when you're dealing with a small number of state variables and straightforward updates. It's easy to use and understand, just a simple getter and setter.

On the other hand, useReducer shines when your state logic is more complex. It allows you to manage state transitions in a more structured way, using a reducer function that handles different action types. This is particularly useful when you have to deal with multiple related state changes or more intricate state management scenarios. Plus, it can help keep your component logic more organized and easier to maintain.

How do you handle routing in a React application?

In a React application, routing is typically handled using a library like React Router. This library allows you to define various routes in your app and map them to specific components. You'd wrap your application in a BrowserRouter component, then use the Route component to specify which paths should render which components. For instance, you might use <Route path="/home" component={HomePage} /> to render the HomePage component when the /home path is accessed. For more dynamic routing, you can also use route parameters and useParams for accessing those parameters within your components. It's quite flexible and integrates well with React.

What's the best way to prepare for a ReactJS interview?

Seeking out a mentor or other expert in your field is a great way to prepare for a ReactJS interview. They can provide you with valuable insights and advice on how to best present yourself during the interview. Additionally, practicing your responses to common interview questions can help you feel more confident and prepared on the day of the interview.

How does server-side rendering (SSR) work in React?

Server-side rendering (SSR) in React involves rendering components on the server instead of in the browser. When a request is made to the server, the React components are converted into HTML. This HTML is then sent to the client, and the browser renders the HTML content. This process can improve performance and SEO, as search engines can easily crawl the pre-rendered HTML.

Once the HTML is loaded on the client side, React takes over, attaching event listeners and making the page interactive. This is often referred to as "hydration." SSR can be implemented using frameworks like Next.js, which simplifies the process and handles many of the tricky parts, like routing and code splitting, for you.

What is Next.js and how does it relate to React?

Next.js is a powerful framework built on top of React that provides features like server-side rendering, static site generation, and API routes. It enhances the capabilities of standard React by optimizing performance and SEO, since content can be rendered on the server and sent to the client as fully-formed HTML.

Next.js also simplifies the development process with features like file-based routing, meaning you don’t have to set up react-router manually. If you’re building a React application and you need better performance and SEO without a lot of manual setup, Next.js is definitely worth considering.

How do you optimize performance in a React application?

Optimizing performance in a React application often involves focusing on key areas like avoiding unnecessary re-renders, optimizing asset loading, and leveraging browser caching. One common technique is using React's memo and useMemo to memoize components and values so they only re-render or recalculate when their dependencies change. Using React's useCallback can also help you avoid unnecessary re-creation of functions.

Another important aspect is code-splitting, which you can do with tools like Webpack or React's React.lazy and Suspense to load parts of your application only when they're needed. Additionally, optimizing images and other static assets can make a big difference, as can server-side rendering (SSR) for faster initial page loads.

Finally, make sure your state management is efficient. Keeping global state to a minimum and using local component state where possible can prevent a lot of unnecessary renders across your app. Profiling tools like React's built-in Profiler or browser dev tools can help identify bottlenecks.

What is React and why is it used?

React is a JavaScript library for building user interfaces, primarily for single-page applications. Developed by Facebook, it's designed to make the process of building interactive and complex UIs more manageable by breaking them down into reusable components. Each component manages its own state and renders UI updates efficiently when the state changes.

It's used because it enables developers to create large web applications that can update and render efficiently in response to data changes. React's virtual DOM improves performance, and its component-based structure enhances maintainability and reusability. It’s also backed by a strong ecosystem and community, with tools and libraries that extend its capabilities further, making it a go-to choice for modern web development.

Explain the concept of JSX and its importance.

JSX stands for JavaScript XML. It's a syntax extension for JavaScript that allows you to write HTML directly within JavaScript. This may look odd at first, but it ultimately makes the code more readable and easier to debug. JSX is not a necessity for using React but it simplifies the component creation process by visually merging JavaScript and HTML.

The importance of JSX comes from its ability to create a visual connection between the structure of your UI and the logic behind it. Instead of splitting up HTML and JavaScript into separate files, JSX allows you to keep the rendering logic embedded within the components themselves. This can foster improved ergonomics for developers, making the codebase cleaner and more intuitive.

How does the virtual DOM work in React?

The virtual DOM in React is like an in-memory representation of the real DOM elements. When you write your components and state changes, React creates a virtual DOM tree and efficiently calculates the minimum number of changes needed to update the real DOM. It does this by performing a diffing algorithm to compare the new virtual DOM with the previous one. Once it has identified the differences, React updates only those specific parts in the real DOM, making the updates very fast and efficient. This process helps in improving the performance and responsiveness of applications.

What are the differences between a class component and a functional component?

Class components in React are ES6 classes that extend from React.Component. They can hold and manage state with this.state and use lifecycle methods like componentDidMount or shouldComponentUpdate. Functional components, on the other hand, are simpler; they are just plain JavaScript functions. Initially, they couldn't manage state or lifecycle methods, but with the introduction of hooks like useState and useEffect, functional components can now handle state and side-effects. This has made them more powerful and often preferred due to their simpler and more concise syntax.

What is the use of the `useState` hook in React?

The useState hook is used to add state to functional components in React. It lets you declare a state variable and a function to update it. The first value is your current state, and the second value is a function that lets you update that state. This hook is particularly useful because it allows functional components to have their own state, which was only possible with class components before hooks were introduced.

How do you handle forms in React?

Handling forms in React typically involves using controlled components. You create a form element with state managed by React. For each form field, you set its value to the corresponding piece of state and update that state on each change using an event handler. This way, React fully controls the form, providing immediate feedback to what the user types.

You might use the useState hook to handle simple forms. For instance, you initialize a state variable to hold the input value, and then you use an onChange event handler to update this state. When the form is submitted, you can either read the state directly or perform further validations and actions on it.

For more complex scenarios, like multi-step forms or forms with validations, integrating libraries like Formik or React Hook Form can be really beneficial. They provide a lot of built-in utilities for handling form state, validation, and submission, making the overall process more streamlined and less error-prone.

Can you explain the `useEffect` hook and its various use cases?

useEffect is a hook in React that lets you perform side effects in functional components. It serves the same purpose as lifecycle methods like componentDidMount, componentDidUpdate, and componentWillUnmount in class components. You can use it for things like fetching data, updating the DOM, or setting up subscriptions.

The first argument to useEffect is a function where you can perform the side effect. The second argument is an optional array of dependencies. If the dependencies change, the effect will re-run. If you pass an empty array, the effect runs only once when the component mounts. You can also return a cleanup function from the effect, which React calls when the component unmounts or before re-running the effect. This is handy for things like clearing timers or unsubscribing from data streams.

You can think of useEffect as a way to tell React, "Run this code after rendering and do it again if these specific values change." This makes it a powerful tool for handling side effects in a functional, declarative way.

What is the Context API and how does it work?

The Context API in React is a way to manage state globally across your application without having to pass props down manually at every level. It allows you to create a context, which you can then provide to the component tree. Components in the tree can then consume this context, giving them access to data that can be shared at different levels.

To use it, you start by creating a context using React.createContext() and then wrap your top-level component with a provider from that context. The provider takes a value prop, which is what you'll be passing down. Any component that needs the data can use the useContext hook to access it. This technique simplifies state sharing and can help avoid prop drilling, making your code cleaner and easier to manage.

What are controlled and uncontrolled components in React?

Controlled components in React are those where the form data is handled by the React component's state. You control the input's value by setting the state, and any changes to the input update the state using an onChange handler. This allows for better control over the form data and makes it easier to perform validation or conditional rendering based on the input.

Uncontrolled components, on the other hand, manage their own state internally. Rather than relying on the React state, you use refs to access the DOM elements directly. They are simpler to set up since you don't need to write state management code, but they give you less control over the form data, which can make things like validation and conditional rendering more cumbersome.

What is the significance of the `key` prop in React lists?

The key prop in React is crucial for keeping track of list items. It helps React identify which items have changed, been added, or removed. This makes the rendering process more efficient because React can update only the elements that have actually changed, rather than re-rendering the entire list. Generally, keys should be unique among siblings and are often best set to a unique identifier from your data, such as an ID. If a stable ID isn't available, you might use the index of the item as a fallback, but that can cause problems if the order of items changes.

Describe the prop drilling problem and how you might solve it.

Prop drilling happens when you pass data through multiple levels of a component tree to get it to the component that actually needs it. It can make the codebase hard to manage and understand because intermediate components need to pass the props down without using them.

To solve this, you could use the Context API, which allows you to create global data that can be accessed anywhere in your component tree without explicit prop passing. Another solution could be state management libraries like Redux or MobX, which provide a more structured way to manage and access state across your application.

What are React fragments and why are they useful?

React Fragments allow you to group multiple elements without adding extra nodes to the DOM. They come in handy when you need to return multiple elements from a component without wrapping them in an additional HTML element like a <div>. This keeps the DOM cleaner and can help with styling, as adding unnecessary nodes can sometimes complicate CSS rules.

You can use React Fragments by either using the shorthand syntax <>...</> or the longer form <React.Fragment>...</React.Fragment>. The choice between these can sometimes come down to whether you need to add a key attribute, which is only possible with the longer form. They’re particularly useful in lists where each element needs a key, helping you manage your component hierarchies without introducing redundant HTML elements.

How do you manage global state in a React application?

Managing global state in a React application can be done in a few ways, but one of the common methods is by using Context API along with useReducer or Context API with custom hooks. Context provides a way to pass data through the component tree without having to pass props down manually at every level. Combining it with useReducer can help manage complex state logic in a more predictable way.

Another popular choice is using state management libraries like Redux or MobX. Redux provides a single source of truth for your application's state, and its predictable state management can be especially useful for large applications. Redux Toolkit has also made working with Redux a lot easier by reducing boilerplate code. MobX is another option that uses observables to track state changes, making it more suitable for applications with highly dynamic state.

For simpler scenarios, you might find that just using the Context API alone is sufficient. Combining it with hooks like useState and useEffect can provide a straightforward way to manage global state without additional libraries.

Can you explain the concept of higher-order components (HOCs)?

Higher-order components (HOCs) in React are functions that take a component and return a new component with additional props or functionality. Essentially, they're a pattern for reusing component logic. It allows you to abstract and share common behavior among components without duplicating code or complicating your component tree.

HOCs don't alter the passed component but wrap it to enhance its behavior. You might use HOCs for things like authentication checks, analytics logging, or fetching and passing data from APIs. A common example is a withLoading HOC that adds loading state management to a component, so it displays a loading spinner until data is fetched.

What are React portals and when would you use them?

React portals provide a way to render children into a DOM node that exists outside the hierarchy of the parent component. Essentially, you can use portals to break out of the parent DOM container and move elements to a different spot in the DOM tree.

You might use portals for situations where the parent component has overflow or z-index issues that you need to overcome, such as modals, tooltips, or dropdowns. It allows you to ensure that these elements render properly without being constrained by the parent component's styles or structure.

Describe the React component lifecycle and its various phases.

The React component lifecycle is divided into several phases: mounting, updating, and unmounting. During the mounting phase, the component is being created and inserted into the DOM. Key lifecycle methods here include constructor(), componentDidMount(), and sometimes getDerivedStateFromProps().

In the updating phase, the component is being re-rendered due to changes in state or props. Here, shouldComponentUpdate(), componentDidUpdate(), and getSnapshotBeforeUpdate() come into play, allowing you to optimize rendering or perform side effects.

Finally, in the unmounting phase, the component is being removed from the DOM. The primary lifecycle method here is componentWillUnmount(), which is used to clean up things like event listeners or timers.

What is the difference between `React.createElement` and JSX?

React.createElement is a method used to create React elements. Essentially, it's the function that underlies JSX. When you write JSX, like <div>Hello, World!</div>, a transpiler like Babel converts that JSX into React.createElement('div', null, 'Hello, World!'). JSX provides a more readable and expressive syntax for writing React components, making the code more intuitive and easier to write, especially for complex UIs.

While React.createElement is more explicit and shows clearly the structure of the elements being created, JSX abstracts this process and resembles HTML, which can make it more approachable for those familiar with web development. However, both ultimately achieve the same result—creating a React element.

What are error boundaries and how do you implement them?

Error boundaries are special React components that catch JavaScript errors anywhere in their child component tree, log those errors, and display a fallback UI instead of crashing the entire app. They are essentially a way to handle exceptions gracefully in a React application.

To implement an error boundary, you create a class component that defines either static getDerivedStateFromError(error) or componentDidCatch(error, info). In getDerivedStateFromError, you can update the state to indicate that an error has occurred, while componentDidCatch is used for logging the error information. You can then use this component to wrap other components.

```jsx class ErrorBoundary extends React.Component { constructor(props) { super(props); this.state = { hasError: false }; }

static getDerivedStateFromError(error) { return { hasError: true }; }

componentDidCatch(error, info) { console.error("Error caught by ErrorBoundary:", error, info); }

render() { if (this.state.hasError) { return

Something went wrong.

; }

return this.props.children;

} } ```

You would then use the ErrorBoundary component to wrap any components that you want to protect from crashing due to errors.

Describe the difference between synthetic and native events in React.

In React, synthetic events are a wrapper around the browser's native events. The idea is to provide a consistent API that works identically across different browsers, which can otherwise have various quirks and inconsistencies. Synthetic events have the same interface as native events, but they also come with features like automatic event pooling, meaning React will reuse event objects for performance improvements.

Native events, on the other hand, are the actual events that the browser emits. When working directly with native events, you deal with raw event handling of the browser's API, which can be prone to inconsistencies across different browsers and requires more manual coding for compatibility.

Using synthetic events abstracts away these inconsistencies, provides better performance via event pooling, and integrates seamlessly with React's lifecycle methods and state management.

What is the purpose of the `react-router-dom` library?

The react-router-dom library is used for handling routing in React applications. It allows you to manage navigation and rendering of different components based on the URL. With react-router-dom, you can create single-page applications (SPAs) where different views or pages are accessible through URLs without the need for a full page refresh. It provides components like <BrowserRouter>, <Route>, <Switch>, and <Link>, making it easy to set up and manage routes and navigation in your app.

Can you explain the concept of lazy loading in React?

Lazy loading in React is a technique that allows you to load components only when they're needed, which can significantly improve the performance of your application. Think of it as saying "don't load everything upfront; wait until it's actually going to be used." This is especially useful for parts of your app that might not be immediately visible to the user, like components behind a modal or in a different route.

React has a built-in React.lazy() function that helps you with this. You can wrap your component with React.lazy and then use it inside a Suspense component, which lets you provide a fallback while the lazy component is being loaded. Here's a quick example:

```jsx const LazyComponent = React.lazy(() => import('./LazyComponent'));

function App() { return ( Loading...

}> ); } ```

In this snippet, LazyComponent will only be loaded when it's actually rendered, and until it's done loading, the user will see the "Loading..." message.

How do you fetch data in a React application?

Fetching data in a React application usually involves using either the fetch API or a library like Axios. You commonly place the data-fetching logic inside a useEffect hook to ensure it runs when the component mounts. For instance:

```jsx import { useEffect, useState } from 'react';

function MyComponent() { const [data, setData] = useState([]);

useEffect(() => { async function fetchData() { try { const response = await fetch('https://api.example.com/data'); const result = await response.json(); setData(result); } catch (error) { console.error('Error fetching data:', error); } }

fetchData();

}, []);

return (

{/ Render your data here /}
); } ```

Using Axios is similar but has a slightly different syntax. Both methods support async/await and handle promises, making them straightforward for asynchronous operations.

What are React hooks and why were they introduced?

React hooks are functions that let you use state and other React features without writing a class. They were introduced to simplify code and make it more modular. Before hooks, you had to manage state and lifecycle methods within class components, which could get pretty bulky and hard to manage as your app grew. With hooks, you can use state and other side effects directly in functional components, making your code easier to read, write, and test. It also promotes reusability by allowing you to extract component logic into reusable functions, called custom hooks.

How do you create a custom hook in React?

Creating a custom hook in React is pretty straightforward. You start by defining a regular JavaScript function, but make sure its name starts with "use" to follow the convention. Inside this function, you can use other hooks like useState or useEffect to encapsulate stateful logic. Finally, you return whatever you need from the custom hook, be it state variables, handlers, or any other piece of reusable logic.

For example, let's say you want to create a custom hook for handling form input. You could define a function called useForm and inside it, use the useState hook to manage the input value. Additionally, you could provide a handleChange function to update the state whenever the input changes. By wrapping this in a custom hook, you make your form handling logic reusable across different components.

Explain the `useRef` hook and its typical use cases.

The useRef hook in React is used to create a mutable object which persists across re-renders. You typically use it to directly access and interact with a DOM element or to store a mutable value that doesn’t cause a re-render when updated. For example, you can use useRef to keep track of focus states, execute animations, or store the previous value of a state.

Here’s a common scenario: if you want to focus an input element when a component mounts, you can create a ref with useRef, attach it to the input element, and then call the focus method in a useEffect hook. Another use case is to keep track of the previous value of a prop or state without triggering a re-render whenever it changes.

What is the purpose of the `memo` function in React?

The memo function in React is used to optimize performance by memoizing a functional component. When you wrap a component with memo, React will only re-render that component if its props change. This can be particularly useful for functional components that are pure, meaning their output is solely dependent on the props they receive. Using memo can help prevent unnecessary re-renders and improve the performance of your application.

What are the best practices for structuring a large React application?

For structuring a large React application, it's a good idea to adopt a modular architecture. Break down your application into smaller, reusable components that are organized by their functionality. Keep your folder structure simple and consistent, like having separate directories for components, services, and utilities.

Using a state management solution like Redux or Context API can help manage the application's state more efficiently, especially as it grows. Also, ensure to keep your styles scoped to their respective components, using CSS-in-JS libraries like styled-components or Emotion for better maintainability. Finally, writing clear and concise component names and using PropTypes or TypeScript for type checking can help in maintaining and scaling the application over time.

Explain the concept of "lifting state up" in React.

"Lifting state up" in React refers to the practice of moving state from child components to a common ancestor, allowing multiple child components to share and update that state. This is useful when you have several components that need to reflect the same changing data. Instead of each child component maintaining its own state, which can lead to inconsistencies, you lift the state to a shared parent component. This parent component then passes the state down to its children as props, ensuring they are always in sync. This approach simplifies the state management and makes the data flow in your application more predictable.

What is the significance of immutability in React state management?

Immutability in React state management is crucial because it enables predictable state updates, which simplifies debugging and makes the app more efficient. When state is immutable, any updates create a new object rather than modifying the existing one. This allows React to efficiently determine what has changed by comparing the old and new state objects, thus optimizing rendering performance with techniques like shouldComponentUpdate and React.memo.

Additionally, immutability helps maintain a clear history of state changes. This is valuable for time-travel debugging and reverting to previous states without side effects, as the original state objects remain unchanged. It also promotes functional programming principles, leading to codebases that are easier to reason about and maintain.

What is the difference between client-side rendering and server-side rendering?

Client-side rendering is when the browser downloads a minimal HTML page and then fetches and runs JavaScript to build the entire content of the site on the client side. This approach often offers more interactive experiences because it can load parts of a page without requiring a full-page reload.

Server-side rendering, on the other hand, involves generating the full HTML for a page on the server in response to requests. This can be faster for the initial load, especially in terms of SEO and time-to-first-byte, as the complete HTML is already available when the browser receives it, making it simpler for search engines to crawl the content.

In brief, client-side rendering can offer a more dynamic user experience but might initially load slower, while server-side rendering can improve initial load times and SEO but might require more server resources.

How do you handle authentication and authorization in a React app?

For handling authentication in a React app, I usually leverage libraries like Firebase, Auth0, or JWT (JSON Web Tokens). They simplify token management and user sessions. Upon successful login, the server sends back a token that I store either in localStorage or a more secure option like HTTP-only cookies, depending on the sensitivity of the data.

Authorization involves controlling what parts of the app a user can access. After authenticating the user and getting their role from the server, I typically use React Router for setting up protected routes. I'll create higher-order components or use context to wrap components that need specific permissions and check if the user role fits the required access level before rendering.

Additionally, I always ensure that sensitive operations have server-side checks even if the client-side restrictions are robust to prevent any unauthorized access through a compromised client.

How do you handle errors in a React application?

Handling errors in a React application can be approached from several angles. For runtime errors in rendering components, React 16 introduced Error Boundaries. You can create an Error Boundary by defining a class component that implements componentDidCatch and static getDerivedStateFromError to catch and handle errors gracefully in the subtree.

For other kinds of errors, like those during asynchronous operations, you typically use try/catch blocks within your async functions. Promises can also be handled using .catch. Additionally, you can leverage global error handling using window.onerror or libraries such as Sentry for comprehensive error logging and monitoring. This way, you can capture uncaught exceptions and take appropriate actions, like displaying user-friendly error messages or sending error details to a logging service.

Get specialized training for your next ReactJS interview

There is no better source of knowledge and motivation than having a personal mentor. Support your interview preparation with a mentor who has been there and done that. Our mentors are top professionals from the best companies in the world.

Only 1 Spot Left

I'm a self-taught Developer with experience at large B2C companies like Deliveroo and Memrise. I have a track record of helping people get their first jobs in Tech as well as upskilling Developers to get significant pay increases. I'm very proud of the work I've done with my mentees, check …

$180 / month
  Chat
1 x Call
Tasks

Only 2 Spots Left

Are you a junior developer looking to fast track your career in web development? Do you need guidance on learning the right and up to date content, building real-world projects, and preparing for job interviews? Look no further! I am a Senior Software Engineer with 9 years of experience in …

$180 / month
  Chat
2 x Calls
Tasks

Only 3 Spots Left

I have over 10 years experience as an engineer and mentor. I have guided hundreds of students into their first development job with a focus on React and TypeScript. I can help you assemble and manage a team of developers or coach you through an individual project. I provide full …

$50 / month
  Chat
1 x Call
Tasks

Only 1 Spot Left

Hello there! I'm Muhib, a seasoned Software Engineer and former Lead Instructor at a top coding boot camp. Over the last three years, I've personally helped over 100 students achieve their goals and build successful careers in tech. I specialize in Full-Stack JavaScript and Python development. With my expertise, I'm …

$150 / month
  Chat
2 x Calls
Tasks

Only 4 Spots Left

Hello, I'm Ben! I've spent the majority of my career as a Software Engineer at Microsoft. I'm passionate about open source, crypto and the future of the web. My day job is spent working on a range of client-side javascript/typescript, mostly related to service workers and react.js. I also have …

$240 / month
  Chat
1 x Call

Only 4 Spots Left

👋 I am Prateek. I am a Senior Engineer at Buffer and a published author of several books on mobile design & software development. In my tenure of over seven years, I have worked on several verticals, which include building native mobile apps for iOS and Android in Swift, Kotlin …

$120 / month
  Chat
1 x Call
Tasks

Browse all ReactJS mentors

Still not convinced?
Don’t just take our word for it

We’ve already delivered 1-on-1 mentorship to thousands of students, professionals, managers and executives. Even better, they’ve left an average rating of 4.9 out of 5 for our mentors.

Find a ReactJS mentor
  • "Naz is an amazing person and a wonderful mentor. She is supportive and knowledgeable with extensive practical experience. Having been a manager at Netflix, she also knows a ton about working with teams at scale. Highly recommended."

  • "Brandon has been supporting me with a software engineering job hunt and has provided amazing value with his industry knowledge, tips unique to my situation and support as I prepared for my interviews and applications."

  • "Sandrina helped me improve as an engineer. Looking back, I took a huge step, beyond my expectations."